SpringSecurity前后端分离,登录逻辑,基于SpringSecurity实现权限管理系统,解决高版本循环依赖和springboot2.6.x与swagger不兼容问题

SpringBoot+Vue驾校管理系统实战:从业务分析到全栈实现 管理信息系统是数字化转型的常见载体,前后端分离架构已成为主流开发范式。SpringBoot后端框架凭借成熟生态和高效开发能力被广泛使用,Vue则用于构建交互式前端界面。在预约类业务场景中,排课冲突、状态流转等核心逻辑尤为关键,而数据库设计与并发控制(如乐观锁)直接决定系统可靠性。驾校管理系统作为典型的多角色业务系统,涵盖学员管理、教练排班、考试流转、收费统计等模块,能够完整展现SpringBoot与Vue在真实业务中的工程化应用。从需求分析到技术选型,再到核心模块实现,本文结合项目实战详细拆解如何构建一个稳 阅读详情
AI摘要

本文介绍基于SpringSecurity实现前后端分离下的权限管理系统,涵盖JWT登录鉴权、验证码集成Redis、Swagger接口文档配置及高版本SpringBoot与Swagger兼容性问题解决方案。重点包括自定义用户认证流程、Token传递机制和循环依赖处理。

先参考文章https://blog.youkuaiyun.com/grd_java/article/details/107584578,了解springSecurity基本操作,另外,数据库也在这篇文章创建了
源码,码云https://gitee.com/yin_zhipeng/spring-security-scaffolding

基本效果说明
  1. 请求login接口,获取token
  2. 获取token后,通过封装名为Authorization的请求头,值为Bearer+空格+token字符串的形式请求需要授权接口(就是请求时,需要带上authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2Mzk0NjA2MTYsImV4cCI6MTYzOTQ2MTMwMCwic3ViIjoiYWRtaW4iLCJjcmVhdGVkIjoxNjM5NDYwNjE2MTI0fQ._oKG2qarbKi84gxdQjHoRSHd3hx-INQn1CscMgAiASW0B6tsPIjWi1LMr35OTtWR-WvQ8R6tRAvkp0Q3RQm0LQ这样的请求头)
  1. authorization是请求头key
  2. Bearer 是我们的一个头标识
  3. 剩下的就是token字符串

一、环境说明(太基本的就不讲了,本文主要讲如何前后端分离模式整合Security)

common除了Spring security模块外,都在这篇文章中有讲解https://blog.youkuaiyun.com/grd_java/article/details/107452826
在这里插入图片描述
在这里插入图片描述
不知道怎么建表,请到文章开头的链接中参考
在这里插入图片描述
在这里插入图片描述

二、配置Security

首先,我们在yml中需要配置一些参数,是我们个人规定好的
  1. 我们规定相应的token 请求头为authorization:Bearer token字符串
    在这里插入图片描述
jwt:
  tokenHeader: Authorization #JWT存储的请求头,请求是这个就是token的key
  secret: ukc8BDbRigUDaY6pZFfWus2jZWLPHO #JWT加解密使用的密钥
  expiration: 684808 #JWT有效时间(60*60*24)
  tokenHead: Bearer #JWT负载中拿到的开头,token字符串,tokenHead是字符串的头,也就是以它开头

1. 用户实体类继承UserDetails接口

在这里插入图片描述

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="DdUser对象,使用Spring Security框架就要继承UserDetails接口,实现方法,将返回值改为true", description="")
public class DdUser implements UserDetails {
    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private Integer id;
    private String username;
    private String password;

    /**
     * 所有权限
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    /**
     * 账号是否过期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 账号是否被锁定
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 凭证(密码)是否过期
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否启用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

2. 编写JWT工具类

在这里插入图片描述

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT工具类
 */
@Component
public class JwtTokenUtil {
    private static final String CLAIM_KEY_USERNAME="sub";//荷载,用户名的key
    private static final String CLAIM_KEY_CREATED="created";//荷载,创建时间key
    /**
     * 项目中推荐使用此形式的密钥和过期时间,因为解耦合
     */
    @Value("${jwt.secret}")
    private String secret;//密钥
    @Value("${jwt.expiration}")
    private Long expiration;//过期时间

    /**
     * 不推荐使用这种,因为写死到了代码中
     */
    public static final long EXPIRE = 1000 * 60 * 60 * 24;                      //设置token过期时间
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";   //设置token密钥,我瞎写的,每个公司都有按自己规则生成的密钥

    //生成token字符串

    /**
     * 根据荷载生成token字符
     * @param id 密言1
     * @param nickname 密言2
     * @return
     */
    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()                                          //构建jwt字符串
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")                             //设置jwt头信息

                .setSubject("guli-user")                                           //分类,名字随便起的,不同的分类可以设置不同的过期
                .setIssuedAt(new Date())                                           //设置过期时间的计时起始值为当前时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))      //设置过期时间为当前时间+EXPIRE我们设定的过期时间

                .claim("id", id)                                                //token主体,这里放你需要的信息,我们实现登陆,就放用户登陆信息
                .claim("nickname", nickname)                                    //需要多少主体信息,就设置多少个claim属性

                .signWith(SignatureAlgorithm.HS256, APP_SECRET)                    //签名哈希,根据指定规则和我们的密钥设定签名
                .compact();

        return JwtToken;
    }

    /**
     * 根据spring security 对象生成token
     * @param userDetails UserDetails对象,spring security会将用户信息保存在这个对象
     * @return
     */
    public String getJwtToken(UserDetails userDetails){

        //分组荷载,也就是主体信息,我们将用户名和有效时间封装到token中
        Map<String,Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED,new Date());

        String JwtToken = gennerateToken(claims);

        return JwtToken;
    }
    private String gennerateToken(Map<String,Object> claims){
        String JwtToken =Jwts.builder()                                          //构建jwt字符串
//                .setHeaderParam("typ", "JWT")
//                .setHeaderParam("alg", "HS256")                             //设置jwt头信息
//
//                .setSubject("dd-user")                                             //分类,名字随便起的,不同的分类可以设置不同的过期
                .setIssuedAt(new Date())                                           //设置过期时间的计时起始值为当前时间
                .setExpiration(new Date(System.currentTimeMillis() + expiration))  //设置过期时间为当前时间+EXPIRE我们设定的过期时间
                .addClaims(claims)
//                .claim(claims)                          //token主体,这里放你需要的信息,我们实现登陆,就放用户登陆信息
//                .claim()                               //需要多少主体信息,就设置多少个claim属性
                .signWith(SignatureAlgorithm.HS512, secret)                    //签名哈希,根据指定规则和我们的密钥设定签名
                .compact();
        return JwtToken;
    }
    /**
     * 从token中获取登录用户名
     */
    public String getUsernameByToken(String token){
        String username;

        try {
            Claims claims = getClaimsFormToken(token); //获取主体内容
            //claims.get(CLAIM_KEY_USERNAME);
            username = claims.getSubject();//获取主体值,上面注释代码和这个都可以,获取的是CLAIM_KEY_USERNAME常量封装的key,对应的值
        }catch (Exception e){
            username = null;
            e.printStackTrace();
        }
        return username;

    }

    /**
     * 从token中获取荷载
     * @param token
     * @return
     */
    private Claims getClaimsFormToken(String token){
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();//解析token字符串,获取主体内容
        }catch (Exception e){
            e.printStackTrace();
        }
        return claims;
    }

    /**
     * 从荷载中获取过期时间
     */
    private Date getExpiredDateFromToken(String token){
        Claims claimsFormToken = getClaimsFormToken(token);
        return claimsFormToken.getExpiration();
    }
    /**
     * 验证token是否失效,失效返回true,没失效返回false
     */
    private boolean isTokenExpired(String token){
        Date expiredDateFromToken = getExpiredDateFromToken(token);
//        boolean before = expiredDateFromToken.before(new Date());
        return expiredDateFromToken.before(new Date());//如果当前时间再过期时间之前,就是还没有到过期时间,返回true,否则返回false
    }

    /**
     * 判断token是否有效
     * @param jwtToken token字符串
     * @return
     */
    public boolean checkToken(String jwtToken,UserDetails userDetails) {
        if(StringUtils.isEmpty(jwtToken)) return false;//如果为空直接返回false表示失效
        String username;
        try {
             username = getUsernameByToken(jwtToken);//根据token获取用户名
        } catch (Exception e) {
            e.printStackTrace();
            username = null;
            return false;//获取不到用户名,直接失效
        }
        if(!username.equals(userDetails.getUsername())) return false;//如果token的用户名与当前UserDetails用户名不一致,直接失效
        if(isTokenExpired(jwtToken)) return false;//如果token失效,返回false
        return true;
    }
    /**
     * 判断token是否可以被刷新
     */
    public boolean canRefresh(String token){
        return !isTokenExpired(token);//已经过期,表示可以被刷新
    }

    /**
     * 刷新token
     */
    public String refreshToken(String token){
        Claims claimsFormToken = getClaimsFormToken(token);
        claimsFormToken.put(CLAIM_KEY_CREATED,new Date());
        return gennerateToken(claimsFormToken);
    }
}

3. 配置JWT过滤器

任何请求都需要进行过滤,如果请求携带我们规定的token,那么将token放在spring Security对象中

在这里插入图片描述

import com.dd.security.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

/**
 * JWT登录授权过滤器,继承OncePerRequestFilter
 */
public class JwtAuthencationTokenFilter extends OncePerRequestFilter {


    @Value("${jwt.tokenHeader}")
    private String tokenHeader;//JWT存储的请求头

    @Value("${jwt.tokenHead}")
    private String tokenHead;//JWT负载中拿到的开头,token字符串,tokenHead是字符串的头,也就是以它开头
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 前置拦截
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
//        Enumeration<String> headerNames = request.getHeaderNames();//debug用的
        //通过tokenHeader获取验证头,也就是tokenHead+token字符串
        String authHeader = request.getHeader(tokenHeader);

        //如果验证头存在,并且token字符串符合我们规定的tokenHead开头
        if(authHeader!=null && authHeader.startsWith(tokenHead)){
            //将token截取出来
            String authToken = authHeader.substring(tokenHead.length());
            //根据token获取用户名
            String username = jwtTokenUtil.getUsernameByToken(authToken);
            //如果token存在,但是Security全局上下文没有用户,表示用户没有登录
            if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
                //登录
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                //如果token有效
                if(jwtTokenUtil.checkToken(authToken,userDetails)){
                    //更新登录用户对象UserDetails
                    UsernamePasswordAuthenticationToken authenticationToken =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    //重新设置到用户对象中
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    //设置到security全局上下文
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }

        filterChain.doFilter(request,response);//放行
    }
}

4. 自定义返回结果

当访问接口没有权限,自定义返回结果

在这里插入图片描述

import com.dd.common_utils.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 当访问接口没有权限,自定义返回结果
 */
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");//设置响应编码
        response.setContentType("application/json");//设置响应格式,前后端分离最好是json
        PrintWriter out = response.getWriter();//获取response输出流
        //创建返回结果对象
        Result result = Result.error().code(403).message("权限不足,请联系管理员");
        //以json字符串形式放到response输出流中
        out.write(new ObjectMapper().writeValueAsString(result));
        out.flush();//刷新
        out.close();//关闭流
    }
}
当未登录或token失效时访问接口,自定义返回结果

在这里插入图片描述

import com.dd.common_utils.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 当未登录或token失效时访问接口,自定义返回结果
 */
@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");//设置响应编码
        response.setContentType("application/json");//设置响应格式,前后端分离最好是json
        PrintWriter out = response.getWriter();//获取response输出流
        //创建返回结果对象
        Result result = Result.error().code(401).message("请登录!!!");
        //以json字符串形式放到response输出流中
        out.write(new ObjectMapper().writeValueAsString(result));
        out.flush();//刷新
        out.close();//关闭流
    }
}

5. Security配置类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import com.dd.security.entity.DdUser;
import com.dd.security.exception.RestAccessDeniedHandler;
import com.dd.security.exception.RestAuthorizationEntryPoint;
import com.dd.security.filter.JwtAuthencationTokenFilter;
import com.dd.security.service.DdUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DdUserService ddUserService;

    @Autowired
    private RestAccessDeniedHandler restAccessDeniedHandler;//当访问接口没有权限,自定义返回结果
    @Autowired
    private RestAuthorizationEntryPoint restAuthorizationEntryPoint;//当未登录或token失效时访问接口,自定义返回结果
    /**
     * 配置密码解析Bean实例
     * 加载到IOC容器
     * 这是Spring Security 自定义登录逻辑的硬性要求
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置UserDetailsService
     */
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return username->{
            DdUser user = ddUserService.getLoginInfoByUsername(username);
            if(user == null){
                return null;
            }
            return user;
        };
    }

    /**
     * 配置JWT登录授权过滤器
     */
    @Bean
    public JwtAuthencationTokenFilter jwtAuthencationTokenFilter(){
        return new JwtAuthencationTokenFilter();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService())//设置Security使用的userDetailsService,为我们上面配置的
                .passwordEncoder(passwordEncoder());//设置使用的passwordEncoder为我们上面配置的passwordEncoder
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //使用JWT不需要csrg
        http.csrf().disable()
                //使用Token,不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()//接下来配置授权
                .authorizeRequests()
                //下面授权在configure(WebSecurity web)方法中配置了,这里不需要了
//                .antMatchers("/login","/logout").permitAll()//允许访问/login,/logout的请求无需认证即可通行
                .anyRequest().authenticated()//除了上面配置的,剩下的请求全部拦截,必须认证通过才能访问
                .and()//接下来配置缓存
                .headers()
                .cacheControl()
                ;
        //添加JWT登录授权过滤拦截器
        http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        //添加自定义未授权,和未登录结果返回,前后端分离,需要返回状态码
        http.exceptionHandling()
                .accessDeniedHandler(restAccessDeniedHandler)
                .authenticationEntryPoint(restAuthorizationEntryPoint);

    }

    /**
     * 放行路径配置
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/login",
                        "/logout",
                        "/css/**",
                        "/js/**",
                        "/swagger-ui.html",
                        "/webjars/**",
                        "/swagger-resources/**",
                        "/v2/api-docs/**"
                );
    }
}

三、实现登录逻辑

1. 专门给登录用户的实体类

在这里插入图片描述

2. controller

在这里插入图片描述

import com.dd.common_utils.Result;
import com.dd.security.entity.DdUser;
import com.dd.security.entity.UserLogin;
import com.dd.security.service.DdUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.security.Principal;

@RestController
@Api(tags = "LoginController,登录")
public class LoginController {

    @Autowired
    private DdUserService ddUserService;

    @ApiOperation(value = "登录之后返回token,如果想要带着token请求,需要添加Bearer前缀,Bearer token,中间空格分隔")
    @PostMapping("/login")
    public Result login(@RequestBody UserLogin userLogin, HttpServletRequest request){
        if(userLogin == null){
            Result.error().message("请输入用户名和密码");
        }
        if(userLogin.getUsername()==null){
            Result.error().message("请输入用户名");
        }
        if(userLogin.getPassword()==null){
            Result.error().message("请输入密码");
        }
        return ddUserService.login(userLogin,request);
    }

    @ApiOperation(value = "获取当前登录用户信息")
    @GetMapping("/login/info")
    public Result loginInfo(@ApiParam(value ="全局对象,security将信息设置到全局,通过这个就可以获取")
                            Principal principal){
        if(principal == null){
            return Result.error().message("用户信息不存在");
        }
        String username = principal.getName();//从全局中获取username
        //根据用户名获取用户对象
        DdUser ddUser =  ddUserService.getLoginInfoByUsername(username);
        if(ddUser == null){
            return Result.error().message("用户信息不存在");
        }
        ddUser.setPassword(null);//无论如何都不可以返回密码
        return Result.ok().message("用户信息获取成功").data("loginInfo",ddUser);
    }

    @ApiOperation(value = "退出登录")
    @PostMapping("/logout")
    public Result logout(){
        //这里省略了退出登录逻辑,比如销毁token,销毁数据库,redis中缓存等,因为没有引入redis,所以直接返回退出成功
        return Result.ok().message("退出登录");
    }
}

3. service

在这里插入图片描述
在这里插入图片描述

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dd.common_utils.Result;
import com.dd.security.entity.DdUser;

import com.dd.security.entity.UserLogin;
import com.dd.security.mapper.DdUserMapper;
import com.dd.security.service.DdUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dd.security.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
@Service
public class DdUserServiceImpl extends ServiceImpl<DdUserMapper, DdUser> implements DdUserService {

    @Autowired
    private DdUserMapper ddUserMapper;

    @Autowired
    private UserDetailsService userDetailsService;//spring security UserDetailsService,需要我们自己实现它

    @Autowired
    private PasswordEncoder passwordEncoder;//PasswordEncoder,需要我们自己配置出来

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Value("${jwt.tokenHead}")
    private String tokenHead;
    /**
     * 登录之后返回token
     */
    @Override
    public Result login(UserLogin userLogin, HttpServletRequest request) {
        //获取userDetails对象,这里我们userDetailsServiceImpl实现的是,它通过用户名查询了数据库的密码
        UserDetails userDetails = userDetailsService.loadUserByUsername(userLogin.getUsername());
        //如果用户为空,
        if(userDetails==null){
            return Result.error().message("用户不存在");
        }
        //密码匹配失败
        if(!passwordEncoder.matches(userLogin.getPassword(),userDetails.getPassword())){
            return Result.error().message("密码错误");
        }
        //下面这个因为我们DdUser将isEnabled写死为true,所以就不写这些判断了,其实都应该写成属性,存储到数据库
//        if(!userDetails.isEnabled()){
//            return Result.error().message("用户被禁言,请联系管理员");
//        }

        //更新登录用户对象UserDetails 如果获取对象成功,没有错误,那么使用Spring Security我们需要将对象设置到UserDetails中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);//将其放在Security全局中
        //生成token,返回
        String jwtToken = jwtTokenUtil.getJwtToken(userDetails);
        return Result.ok().message("登录成功,获取token")
                .data("token",jwtToken)//token字符串
                .data("tokenHead",tokenHead);//tokenheader,key
    }

    /**
     * 根据用户名获取登录对象
     * @param username
     * @return
     */
    @Override
    public DdUser getLoginInfoByUsername(String username) {
        DdUser ddUser = ddUserMapper.selectOne(new QueryWrapper<DdUser>().eq("username", username));
        if(ddUser == null){
            return null;
        }
        return ddUser;
    }
}

四、通过swagger测试

1. 配置swagger,携带指定请求头authorization,规定哪些路径需要认证(只有规定的路径才会携带请求头)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

package com.dd.service_base.config;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableSwagger2//表中此类为Swagger2
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig() {
        List<Parameter> pars = new ArrayList<Parameter>();
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")//分组名
                .apiInfo(webApiInfo())//在线文档的信息,传入ApiInfo对象,就是下面内个方法返回的对象
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))//路径中包含admin时不显示信息
                .paths(Predicates.not(PathSelectors.regex("/error/.*")))
                .build()
                //因为我们开启了securityJWT验证,所以我们也需要给Swagger配置头信息
                .securityContexts(securityContexts())
                .securitySchemes(securitySchemes())
                ;
    }

    private ApiInfo webApiInfo() {
        return new ApiInfoBuilder()
                .title("gulischool 接口 API 文档")
                .description("展示先做基础功能,后面再添加业务")
                .termsOfServiceUrl("https://www.dd.com/aa/")
                .version("1.0")
                .contact(new Contact("Helen","http://dd.com","915501928@qq.com"))
                .build();
    }


    private List<ApiKey> securitySchemes(){
        //设置头信息
        ArrayList<ApiKey> result = new ArrayList<>();
        ApiKey apiKey = new ApiKey("Authorization", "Authorization", "Header");
        result.add(apiKey);
        return result;
    }

    /**
     * 设置哪些路径需要授权
     * @return
     */
    private List<SecurityContext> securityContexts(){
        //设置需要授权认证的路径
        ArrayList<SecurityContext> result = new ArrayList<>();
        result.add(getContextByPath("/service_animation/.*"));
//        result.add(getContextByPath("/login/.*"));
        result.add(getContextByPath("/login/info"));
        result.add(getContextByPath("/logout"));
        result.add(getContextByPath("/security/*"));
        return result;
    }

    private SecurityContext getContextByPath(String pathRegex) {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
//                .forPaths(PathSelectors.regex(pathRegex))
                .forPaths(PathSelectors.regex("^(?!auth).*$"))
                .build();
    }

    private List<SecurityReference> defaultAuth() {
        ArrayList<SecurityReference> result = new ArrayList<>();
        //设置授权范围为全局global
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");

        //官方要求必须放在数组中,所以将其放在数组中
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0]=authorizationScope;
        //官方要求List中添加new SecurityReference("Authorization",authorizationScopes)
        result.add(new SecurityReference("Authorization",authorizationScopes));
        return result;
    }
}

2. 测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

前端呢,只需要登录后按规则保存token,请求时,携带这个token就可以访问了

五、验证码实现

更改实体类,我们登录时需要传入验证码了

在这里插入图片描述

引入依赖

在这里插入图片描述

1. 生成验证码

验证码配置类

在这里插入图片描述

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
 * 验证码配置类
 */
@Configuration
public class CaptchaConfig {
    @Bean
    public DefaultKaptcha defaultKaptcha(){
        //验证码生成器
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        //配置
        Properties properties = new Properties();
        //时候有边框
        properties.setProperty("kaptcha.border","yes");
        //设置边框颜色
        properties.setProperty("kaptcha.border.color","105,179,98");
        //验证码
        properties.setProperty("kaptcha.session.key","code");
        //验证码文本字符颜色,默认黑
        properties.setProperty("kaptcha.textproducer.font.color","blue");
        //设置字体样式
        properties.setProperty("kaptcha.textproducer.font.names","宋体,楷体,微软雅黑");
        //设置字体大小默认40
        properties.setProperty("kaptcha.textproducer.font.size","30");
        //验证码文本字符内容范围 默认abced2345678gfynmnpwx
//        properties.setProperty("kaptcha.textproducer.char.string","");
        //字符长度,默认5
        properties.setProperty("kaptcha.textproducer.char.length","4");
        //字符间距,默认2
        properties.setProperty("kaptcha,textproducer.char.space","4");
        //验证码图片宽度,默认208
        properties.setProperty("kaptcha.image.width","100");
        //验证码图片高度,默认40
        properties.setProperty("kaptcha.image.height","40");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}
controller

在这里插入图片描述

import com.google.code.kaptcha.impl.DefaultKaptcha;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;

@RestController
public class CaptchaController {
    @Autowired
    private DefaultKaptcha defaultKaptcha;

    @ApiOperation(value = "验证码,以image/jpeg格式固定响应")
    @GetMapping(value = "/captcha",produces = "image/jpeg")
    public void captcha(HttpServletRequest request, HttpServletResponse response)  {

        /**定义response输出类型为image/jpeg类型**/
        response.setDateHeader("Expires",0);
        // Set standard HTTP/1.1 no-cache headers.
        response.setHeader("Cache-Control","no-store, no-cache, must-revalidate");
        // Set IE extended HTTP/1.1 no-cache headers(use addHeader)
        response.addHeader("Cache-Control","post-check=0,pre-check=0");
        // Set standard HTTP/1.0 no-cache header.
        response.setHeader("Pragma","no-cache");
        // return a jpeg
        response.setContentType("image/jpeg");
        /**生成验证码 begin**/
        String text = defaultKaptcha.createText();
        System.out.println("验证码:"+text);
        //将验证码文本内容放入session
        request.getSession().setAttribute("captcha",text);
        //根据文本验证码内容创建图形验证码
        BufferedImage image = defaultKaptcha.createImage(text);
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            //输出流输出.jpg格式图片
            ImageIO.write(image,"jpg",outputStream);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
swagger测试

在这里插入图片描述
在这里插入图片描述

2. 修改service逻辑

在这里插入图片描述

        //从session中获取验证码
        String captcha = (String) request.getSession().getAttribute("captcha");
        //如果验证码为空,或者验证码和session中验证码不一致(忽略大小写比较),返回错误
        if(StringUtils.isEmpty(captcha)||!captcha.equalsIgnoreCase(userLogin.getCode())){
            return Result.error().message("验证码错误!!!");
        }

3. 使用redis改造,将验证码存储到redis中

为了节省篇幅,将redis的改造放在下面实现权限管理系统的文章中

六、实现权限管理系统

因为篇幅限制,我将其放在这篇文章中https://blog.youkuaiyun.com/grd_java/article/details/121932440

七、解决高版本循环依赖和springboot2.6.x与swagger不兼容问题

实现了权限管理系统后,出现高版本循环依赖的问题,如下
  1. 当spring boot版本为2.6.X以上时,出现问题
    在这里插入图片描述
    在这里插入图片描述
原因如下
  1. SecurityConfig配置类中,注入了private DdUserService ddUserService;
    在这里插入图片描述
  2. DdUserServiceImpl又注入了UserDetailsService
    在这里插入图片描述
解决方案
  1. SecurityConfig注释private DdUserService ddUserService;,然后使用DdUserMapper
    在这里插入图片描述
    在这里插入图片描述
循环依赖解决,但是又出现了最近经常出现的问题,springboot2.6.x与swagger2不兼容的问题,这个和咱代码就没关系了,解决方案如下,修改配置,或者升级swagger或者降低springboot版本到2.5.x
  1. 解决循环依赖,新问题如下,表面上是swagger2找不到东西了,其实是ant_path_matcher参数的问题
    在这里插入图片描述
  2. 配置yaml参数
    在这里插入图片描述
spring:
  main:
    allow-circular-references: true # 关闭spring boot 循环注入检测
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher # 解决spring boot 高版本不兼容swagger2.x版本问题
  1. 问题解决,项目跑起来了,但是此时swagger不能用了,显示跨域问题
    在这里插入图片描述
  2. 修改跨域配置
    在这里插入图片描述
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
//                .allowedOrigins("*")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }
SpringBoot CRM系统源码全解析:从架构设计到权限控制 在现代企业级应用开发中,SpringBoot凭借其简化配置与内嵌容器特性,已成为Java后端的主流框架选择。而CRM客户关系管理系统作为典型的业务管理系统,其技术架构与权限设计往往反映了真实工程实践中的通用解决方案。基于SpringBoot构建的CRM系统通常采用MyBatis-Plus作为持久层框架,以提升CRUD开发效率;借助Spring Security与JWT实现无状态认证与RBAC权限控制,保障多角色数据安全。同时,数据权限的动态SQL隔离、客户公海机制、状态机流转等设计,也为处理复杂业务场景提供 阅读详情

相关推荐

Android 12 HAL开发避坑实录:手把手教你用AIDL在Native层实现一个可开机自启动的Service

本文详细介绍了在Android 12 HAL开发中,如何通过AIDL在Native层实现一个可开机自启动的Service。从环境准备、AIDL接口定义、服务实现到VINTF集成与SELinux策略配置,提供了完整的实战指南,帮助开发者避开常见陷阱,提升开发效率。

weixin_28314575的博客 321

SpringBoot中Security和Swagger默认运行中的问题总结

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Mar

qq_31986131的博客 1379

碳酸钙岩石酸蚀酸溶COMSOL建模:非均质对酸蚀通道的影响

多孔介质中的流动与化学反应耦合是石油工程和地下资源开发中的核心问题,酸液与岩石矿物的溶蚀过程直接关系到酸化增产效果。在碳酸盐岩储层中,盐酸与方解石的反应速率极快,酸液会优先沿高渗透区域推进,形成指状酸蚀通道,即wormhole。传统均质模型难以描述这种局部溶蚀行为,而数值模拟工具COMSOL通过耦合达西定律、稀物质传递与孔隙度演化方程,能够定量刻画酸蚀路径与渗透率的变化规律。工程实践中,模拟结果可用于预测酸液突破体积、优化注入排量,并为基质酸化方案设计提供依据。本文围绕碳酸钙岩石酸蚀酸溶COMSOL模型,探

weixin_29009401的博客 327

zuul网关,springsecurity认证中心 和 Swagger2 整合遇到的问题

背景 前后端分离项目,后台有若干个接口,并且已经集成了认证中心,每个接口都实现swagger,现在实现的功能是在一个入口处查看各个子服务的信息。 效果如图所示,网关端口是9999,下面先弄了3个子服务。 遇到的问题 1.依赖问题 因为依赖的版本和springboot有冲突,导致swagger一直报错,springboot2X版本和swagger老版本好像有兼容性问题,现在统一引入下面的版本。 <dependency> <groupId>.

入门菜鸟程序员的成长 945

高分7(GF7)卫星数据制作平原地区DSM/DEM(三)——可信度分割

李国春(接上)

副组长的博客 1714

Unity 数字孪生笔记1 工具介绍

前言 工欲善其事必先利其器 流程思考 数字孪生本质上一种基于实际物理数据的可视化方案,通过对接实际数据,在三维界面中展示孪生结果。 那么这个流程就很简单了。 整个流程分为三块:数据来源-》数据分析-》数据展示 输入数据 首先我们需要考虑我们能拿到什么。第一,实际物体的三维数据从哪里来,第二,实际数据怎么获取到。 模型数据 工业上常常会用到CAD模型,CAD模型具有精确、统一的特点,但是CAD不是三角面片,我们不能直接导出到Unity中,而且导入之后有很多不合理的地方,例如面片杂乱、面数过高等问题。针对这个

Thissky的专栏 1万+

SpringBoot 2.6.0发布:禁止循环依赖,还有哪些实用的更新?

点击关注公众号,实用技术文章及时了解Spring Boot 2.6.0已经正式发布,快看看作了哪些升级。1、禁止了循环依赖循环依赖问题一直困扰大家,也是面试常问题目之一,对循环依赖不了解的...

Java知音 2135

优快云余额充值前必读:这5个隐藏条款,90%的开发者都忽略了(附iOS内购避坑指南)

本文揭示了优快云余额充值中90%开发者忽略的5个隐藏条款,包括不可退款规则、iOS内购限制、账号安全责任等关键内容,特别提供了iOS内购避坑指南,帮助开发者避免资金损失和账号风险。

weixin_26765287的博客 147

SpringSecurity实现权限管理系统,解决高版本循环依赖和springboot2.6.x与swagger不兼容问题

通过Spring Security快速实现权限管理系统

殷志鹏的博客 1736

【雕爷学编程】Arduino动手做(71)---红外遥控器及接收模块3

雕爷学编程,Arduino动手做,开源硬件,创客传感器,红外遥控器及接收模块

雕爷学编程 2287

〔重庆理工大学〕计算机网络实验指导书【实验九 直连路由,静态路由,默认路由配置】

本实验指导书介绍了直连路由、静态路由和默认路由的配置方法。主要内容包括:1)路由器基础知识,包括直连路由、静态路由和动态路由三种路由生成方式;2)路由度量值与匹配顺序,解释了不同路由类型的优先级;3)实验任务分为三个部分:直连路由的查看分析、静态路由的配置以及默认路由的配置,详细说明了配置步骤和命令。实验要求通过模拟器搭建网络环境,完成路由配置使PC间能够互通。实验旨在帮助学生掌握不同路由类型的工作原理和配置方法,适用于简单的网络环境。

Pretty Boy Fox的博客 1056

使用spring Security + JWT 实现前后端分离登录(纯代码无原理)

构建项目 代码已上传gitee 如果你看到了该文章说明对spring框架有一定了解,相信你已经知道如何通过springboot进行项目构建 springboot想要支持安全验证以及web支持只需要在pom(如果是maven)文件中引入以下starter maven //安全包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starte

良子的博客 5232

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

xx管理系统之JAVA后端

项目框架:SpringBoot+SpringSecurity+jwt+mybatis+swagger2 前端框架:vue+element 1.idea 新建SpringBoot项目 初始结构如下 导入pom.xml依赖配置 <properties> <java.version>1.8</java.version> <spring.version>2.3.5.RELEASE</spring.version>

qq_40286424的博客 629

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

SpringBoot3+Vue3前后端分离博客系统:从环境搭建到功能测试全流程指南

这次我们来看一个基于 SpringBoot3 和 Vue3 的前后端分离博客系统。对于 2026 届的 Java 毕业生来说,这是一个非常典型的毕业设计选题,它涵盖了现代企业级 Web 开发的核心技术栈。这个项目的重点不在于概念有多新,而在于它能否让你在本地环境顺利跑起来,并理解前后端分离架构的完整工作流程。 这个项目是一个个人博客系统,后端采用 SpringBoot3 框架,前端使用 Vue3 构建,两者通过 RESTful API 进行通信。从 GitHub 仓库信息来看,项目状态为“正在开发中”,这意

dengyulong的专栏 393

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Springboot超市商品销售管理系统37x2w(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

因此,开发一套基于Spring Boot的超市商品销售管理系统,旨在通过数字化、智能化的手段,实现商品销售信息的实时更新、精准分析和高效管理,从而提升超市的运营效率和客户服务水平。[8]杨梵.软件测试技术的关键能力培养探讨[J].福建电脑,2022,38(09):71-74.DOI:10.16707/j.cnki.fjpc.2022.09.016.[4] 李斌,邓思思,蔡思婷,陈琳敏,崔春兰,罗群.大数据时代煤田勘探钻孔地质空间数据库设计与实现[J].自然资源信息化,2022(01):19-24.

laodeng22567的博客 1204

基于springboot家具定制管理系统设计开发

注:实际开发需根据需求调整模块细节,建议结合UML图完善设计文档。

java python定制开发 568

Spring Boot 3 + Spring Security 6 整合:JWT登录校验与RBAC权限认证实战

在分布式架构与前后端分离日趋主流的今天,接口安全已成为后端开发的基石。认证解决“你是谁”,授权决定“你能做什么”,二者共同构成访问控制的核心逻辑。Spring Security 作为业界标准安全框架,通过过滤器链与 SecurityContext 机制,将认证、授权、防护等能力抽象为可扩展组件,帮助企业快速构建高可控的权限体系。结合 JWT(无状态令牌)与 RBAC 权限模型,既能支撑微服务场景下的横向扩展,又能实现细粒度的方法级鉴权。从最基础的登录校验到动态权限配置,从过滤器编写到异常处理,这套方案广泛应

weixin_33985507的博客 212

基于springboot+vue的JavaWeb的水资源管理系统

Spring Boot 2.7.x(简化配置和依赖管理),Spring MVC(Web层),Spring Data JPA(数据库操作)。:Thymeleaf(模板引擎)或Vue.js(可选前后端分离方案),Bootstrap(响应式布局)。:Lombok(简化代码),Swagger(API文档生成),Log4j2(日志管理)。:MySQL 8.0(关系型数据库),Redis(缓存高频访问数据如水质监测结果)。:Spring Security(权限控制,支持OAuth2.0)。

java python定制开发 407

SpringBoot汽车维修管理系统毕设:从数据库设计到答辩完整指南

在软件开发工程实践中,后端框架的选型与业务建模能力是决定项目质量的核心因素。SpringBoot凭借自动配置、内嵌服务器与丰富的starter生态,大幅降低了环境搭建成本,使开发者能够更专注于业务逻辑实现。本文从技术原理出发,围绕一个典型的汽车维修管理系统,系统梳理了基于SpringBoot结合MyBatis-Plus的持久层设计方案,包含数据库表结构、工单状态机、事务与并发控制等关键设计。同时,针对前后端分离场景下的JWT鉴权、跨域处理、接口联调等工程实践问题给出了可落地的解决方案。该案例适用于高校毕业设

weixin_34343000的博客 228

基于springboot共享便捷城市图书借阅管理系统

Spring Boot 2.7.x(提供快速开发能力,集成Spring MVC、Spring Data JPA等)MySQL 8.0(主数据库存储用户、图书、借阅信息)MongoDB(可选,存储图书评论等非结构化数据)Spring Security(用户认证与授权)JUnit 5 + Mockito(单元测试)Spring Data JPA(数据库操作)Redis(缓存借阅记录、热门图书)Vue.js 3.x(前端交互)Axios(HTTP请求)Docker(容器化部署)Lombok(简化代码)

540

SpringBoot+Vue教学平台开发全攻略

前后端分离架构是现代Web开发的典型范式,通过SpringBoot提供RESTful API接口,Vue.js构建动态前端,实现业务逻辑与展示层的解耦。这种架构模式在Java Web开发中具有显著优势:SpringBoot的自动配置简化了后端开发,Vue的响应式特性提升了前端体验。教学管理系统作为常见应用场景,需要处理用户权限管理、文件上传等核心功能,采用RBAC模型和组件化开发能有效保证系统扩展性。本文详解的SpringBoot+Vue技术方案,既适合作为毕业设计项目模板,也能满足实际教学管理需求,其中数

weixin_33811539的博客 331

springboot的汽车维修预约服务管理系统

注:完整源码需结合具体业务需求调整,建议通过GitHub等平台搜索成熟开源项目参考实现。Spring Boot 2.7.x(基于Java 17)Spring Security(认证与授权)Spring Data JPA(数据库操作)Thymeleaf(服务端渲染模板)Bootstrap 5(响应式UI)jQuery/Ajax(动态交互)MySQL 8.0(关系型数据库)Hibernate(ORM框架)Redis(缓存会话管理)JUnit 5(单元测试)Postman(接口测试)Lombok(简化代码)

java python定制开发 340

Springboot大学生志愿者服务管理系统0nkeb(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

设计并实现一个基于SpringBoot的大学生志愿者服务管理系统,覆盖志愿者注册、活动发布、报名审核、服务记录、积分管理、数据统计等核心功能,解决传统管理模式中的信息孤岛、效率低下等问题。[2]赵梓皓,崔应留,葛晨,沈盈之,雷妤婷.基于SpringBoot的社区防控管理系统的设计与实现[J].软件,2022,43(10):154-159.[11]凌美霞,陈嘉雯,张玲,宗慧琳,林小芳,沈丹.南通市智慧社区建设研究[J].中国标准化,2022,(S1):295-299.确定方案,完成开题报告。

Dangdangcu的博客 1006

第3章 从0开始用若依

若依(RuoYi)框架是一款基于Spring Boot、Spring Cloud等开源框架搭建的企业级开发平台。它的主要目标是提供全面的解决方案,以简化企业级应用开发提高开发效率。模块化设计:若依框架采用模块化的设计,使得应用可以根据需求进行灵活的扩展和定制。每个模块都可以独立部署和升级,这大大提高了系统的可维护性和可扩展性。前后端分离:框架采用前后端分离的开发模式,后端专注于数据处理和API接口的提供,前端则负责页面的展示和用户交互。这种模式使得前后端职责明确,进一步提高了开发效率和代码的可维护性。

m0_73919023的博客 284

springboot2.6.x版本的springsecurity注入PasswordEncoder导致循环依赖问题

按照以前项目搭建框架,启动报错 The dependencies of some of the beans in the application context form a cycle: 目测是循环依赖什么的问题, 在userSecurityConfig注入了UserService 在UserService中使用了PasswordEncoder 在userSecurityConfig继承的SecurityConfig中声明了一个Bean,PasswordEncoder 我理解的是:这就造成了,在创建

weixin_42260782的博客 2590
上一篇: 使用git 提交项目到码云,从码云拉取镜像
下一篇: SpringSecurity实现权限管理系统,解决高版本循环依赖和springboot2.6.x与swagger不兼容问题
ydenergy_殷志鹏
博客等级 码龄7年 5885粉丝 543原创
评论 8
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ydenergy_殷志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值