企业级 SpringBoot3+SpringSecurity6+jwt+redis的项目框架

前言

随着Spring Boot和Spring Security的不断演进,以及JWT(JSON Web Tokens)和Redis等技术的广泛应用,我们迎来了一个全新的、更加强大和灵活的安全认证与授权时代。本文将详细搭建SpringBoot3+SpringSecurity6+JWT+Redis项目框架的全过程,从理论学习到项目实战,从遇到问题到解决问题的每一个细节。

所用到的技术

  • springboot 3.2.5
  • java jdk 17
  • springsecurity6
  • springJPA
  • mybatis
  • lombok
  • redis 3.2
  • fastjson
  • knife4j
  • mysql 5.7.24

导入依赖

        <!--mysql依赖-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>3.0.3</version>
            <scope>test</scope>
        </dependency>

        <!--引入druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>5.3.22</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-core</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-spring-web</artifactId>
            <version>3.0.0</version>
        </dependency>
         <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--fastjson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <!--jpa依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!--引入SpringSecurity-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

RBAC权限模型

RBAC(Role-Based Access Control,基于角色的访问控制)是一种广泛使用的权限控制模型,它通过将权限分配给角色,而不是直接授予用户,从而简化了权限管理。在RBAC模型中,用户通过成为某个角色的成员来获得相应的权限,而这些权限定义了用户能够执行的操作或对资源的访问级别。

用户表

CREATE TABLE `user` (
  `id` bigint(11) NOT NULL COMMENT '主键',
  `name` varchar(255) DEFAULT NULL COMMENT '姓名',
  `username` varchar(255) DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) DEFAULT NULL COMMENT '密码',
  `status` int(11) DEFAULT NULL COMMENT '0:禁用  1:启用',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `create_by` int(11) DEFAULT NULL COMMENT '创建人id',
  `update_by` int(11) DEFAULT NULL COMMENT '修改人id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息';

角色表

CREATE TABLE `role` (
  `id` bigint(20) NOT NULL COMMENT '主键',
  `role` varchar(255) DEFAULT NULL COMMENT '角色名称',
  `name` varchar(255) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

权限表

CREATE TABLE `menu` (
  `id` bigint(20) NOT NULL COMMENT '主键',
  `permission` varchar(255) DEFAULT NULL COMMENT '权限',
  `name` varchar(255) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

用户--角色表

CREATE TABLE `user_role` (
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
  `role_id` bigint(20) DEFAULT NULL COMMENT '角色id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

角色--权限表

CREATE TABLE `role_menu` (
  `role_id` bigint(20) DEFAULT NULL COMMENT '角色id',
  `permission_id` bigint(20) DEFAULT NULL COMMENT '权限id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

前期准备

后端统一响应结果

@Data
public class Result<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private T data; //数据

    public static <T> Result<T> success() {
        Result<T> result = new Result<T>();
        result.code = 1;
        return result;
    }

    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }
}

jwt工具类

public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);
        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }

}

常量

/**
 * JWT中存放的Claims常量
 * @author gs
 */
public class JwtClaimsConstant {
    public static final String USER_ID = "userId";
    public static final String USERNAME = "username";
    public static final String NAME = "name";
    public static final String TOKEN = "token";
}

/**
 * 消息提示类常量
 * @author gs
 */
public class LoginConstant {
    public static final String USER_NOT_FOUND = "用户不存在";
    public static final String USER_NOT_ENABLE = "用户未启用";
    public static final String USER_NOT_LOGIN = "用户未登录";
    public static final String USERNAME_IS_NULL = "用户名为空";
    public static final String UNKNOWN_ERROR = "未知错误";
    public static final String USER_LOGIN_SUCCESS = "用户登录成功";
    public static final String LOGIN_FAILED = "登录失败";
    public static final String PASSWORD_ERROR = "密码错误";
    public static final String USERNAME_OR_PASSWORD_ERROR = "用户名或密码错误";
    public static final String ANONYMOUS_USER_NO_PERMISSION = "匿名用户无权限访问";
    public static final String LOGOUT_SUCCESS = "退出成功";
}

/**
 * 权限问题
 * @author gs
 */
public class PermissionConstant {

    public static final String PermissionDenied = "权限不足";
}

/**
 * redis常量
 */
public class RedisConstant {

    // 存储在redis中的token前缀
    public static final String TOKEN_HEADER = "gs_";
}

/**
 * token异常处理常量
 * @author gs
 */
public class TokenConstant {
    // token为空
    public static final String TOKEN_IS_NULL = "token为空";
    // token无效
    public static final String TOKEN_INVALID = "token无效";
    // token过期
    public static final String TOKEN_EXPIRED = "token过期";
}

实体类

dto

@Data
@ApiModel(description = "用户登录信息")
public class UserLoginDTO implements Serializable {

    @ApiModelProperty(value = "用户名", required = true)
    private String username;

    @ApiModelProperty(value = "密码", required = true)
    private String password;
}

实体


@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user")
@Entity
public class User {

    // 用户唯一标识
    @Id
    private Long id;

    // 姓名
    private String name;

    // 用户名
    private String username;

    // 密码
    private String password;

    // 状态: 0:禁用 1:启用
    private Integer status;

    // 创建时间
    private LocalDateTime createTime;

    // 更新时间
    private LocalDateTime updateTime;

    // 创建人
    private Integer createBy;

    // 更新人
    private Integer updateBy;

}

vo

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(description = "用户登录返回信息")
public class UserLoginVO implements Serializable {

    @ApiModelProperty("主键值")
    private Long id;

    @ApiModelProperty("jwt令牌")
    private String token;

    @ApiModelProperty("姓名")
    private String name;

    @ApiModelProperty("用户名")
    private String username;

}

config配置类

knife4j配置类

@Configuration
public class Knife4jConfig {//对于配置类要求可以看懂即可,不用反复去写,将来可以CV
    @Bean
    public OpenAPI springShopOpenApi() {
        return new OpenAPI()
                // 接口文档标题
                .info(new Info().title("标题")
                        // 接口文档简介
                        .description("项目简介")
                        // 接口文档版本
                        .version("1.0.0")
                        // 开发者联系方式
                        .contact(new Contact().name("顾随")
                                .email("****")));

    }
}

redis配置

@Configuration
public class RedisConfig {

    /**
     * 自定义redisTemplate
     * @return
     */
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        //设置redis连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置redis key的序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

Mapper

MenuMapper

@Mapper
public interface MenuMapper {

    /**
     * 根据用户id获取权限
     * @param userId
     * @return
     */
    @Select("SELECT\n" +
            "m.permission \n" +
            "FROM\n" +
            "user_role ur\n" +
            "INNER JOIN role r ON ur.role_id = r.id\n" +
            "INNER JOIN role_menu rm ON rm.role_id = r.id\n" +
            "INNER JOIN menu m ON m.id = rm.permission_id \n" +
            "WHERE\n" +
            "user_id = #{userId}")
    List<String> selectPermissionByUserId(Long userId);
}

UserMapper

@Mapper
public interface UserMapper {

    /**
     * 根据用户名查询用户
     * @param username
     * @return
     */
    @Select("select * from user where username = #{username} and status = 1")
    User findByUsername(String username);

    /**
     * 根据id查询用户
     * @param id
     * @return
     */
    @Select("select * from user where id = #{id} and status = 1")
    User findById(Long id);
}

repository

public interface UserRepository extends JpaRepository<User, Long> {

    /**
     * 根据用户名查询用户
     * @param username 用户名
     * @return 用户
     */
    User findByUsername(String username);
}

异常处理

匿名用户无权限访问资源处理类

@Component
public class AnonymousAuthenticationHandler implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        //设置客户端的响应的内容类型
        response.setContentType("application/json;charset=UTF-8");

        String result = null;
        ServletOutputStream outputStream = response.getOutputStream();
        // 消除循环引用

        if (authException instanceof BadCredentialsException) {
            result = JSON.toJSONString(Result.error(LoginConstant.USERNAME_OR_PASSWORD_ERROR), SerializerFeature.DisableCircularReferenceDetect);

        } else if (authException instanceof InternalAuthenticationServiceException) {
            result = JSON.toJSONString(Result.error(LoginConstant.USERNAME_IS_NULL), SerializerFeature.DisableCircularReferenceDetect);
        } else {
            result = JSON.toJSONString(Result.error(LoginConstant.ANONYMOUS_USER_NO_PERMISSION), SerializerFeature.DisableCircularReferenceDetect);
        }

        outputStream.write(result.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

认证用户无权限访问的处理器

@Component
public class CustomerAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        // 发生这个异常,做出处理
        response.setContentType("application/json;charset=utf-8");
        // 获取输出流
        ServletOutputStream outputStream = response.getOutputStream();
        // 消除循环引用
        String result = JSON.toJSONString(Result.error(PermissionConstant.PermissionDenied), SerializerFeature.DisableCircularReferenceDetect);
        outputStream.write(result.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

用户认证校验失败的处理器

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        //设置客户端响应编码格式
        response.setContentType("application/json;charset=UTF-8");
        //获取输出流
        ServletOutputStream outputStream= response.getOutputStream();
        String message = null;//提示信息

        //判断异常类型
        if(exception instanceof AccountExpiredException){
            message = "账户过期,登录失败!";
        }else if(exception instanceof BadCredentialsException){
            message = "用户名或密码错误,登录失败!";
        }else if(exception instanceof CredentialsExpiredException){
            message = "密码过期,登录失败!";
        }else if(exception instanceof DisabledException){
            message = "账户被禁用,登录失败!";
        }else if(exception instanceof LockedException){
            message = "账户被锁,登录失败!";
        }else if(exception instanceof InternalAuthenticationServiceException){
            message = "账户不存在,登录失败!";
        }else if(exception instanceof CustomerAuthenticationException){
            message = exception.getMessage();
        }else{
            message = "登录失败!";
        }
        //将错误信息转换成JSON
        String result = JSON.toJSONString(Result.error(message));
        outputStream.write(result.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

自定义验证异常

public class CustomerAuthenticationException extends AuthenticationException {
    public CustomerAuthenticationException(String msg) {
        super(msg);
    }
}

jwt令牌过滤器

@Component
@RequiredArgsConstructor
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    // 登录失败的处理器
    private final LoginFailureHandler loginFailureHandler;

    // jwt变量
    private final JwtProperties jwtProperties;

    // 用户的mapper
    private final UserMapper userMapper;

    // 权限
    private final MenuMapper menuMapper;

    // redis配置
    private final RedisTemplate redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            String uri = request.getRequestURI();
            // 如果是登录接口,直接放行
            if (!uri.startsWith("/user/login")) {
                this.validateToken(request, response);
            }
        } catch (AuthenticationException e) {
            loginFailureHandler.onAuthenticationFailure(request, response, e);
        }

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

    /**
     * token的校验
     *
     * @param request
     * @param response
     */
    public void validateToken(HttpServletRequest request, HttpServletResponse response) {
        String token = request.getHeader(jwtProperties.getUserTokenName());

        //如果请求头部没有获取到token,则从请求的参数中进行获取
        if (ObjectUtils.isEmpty(token)) {
            token = request.getParameter(jwtProperties.getUserTokenName());
        }
        if (ObjectUtils.isEmpty(token)) {
            // 不通过,响应401状态码
            response.setStatus(401);
            throw new CustomerAuthenticationException(TokenConstant.TOKEN_IS_NULL);
        }

        // redis进校验
        String redisToken = redisTemplate.opsForValue().get(RedisConstant.TOKEN_HEADER + token).toString();

        if (ObjectUtils.isEmpty(redisToken)) {
            response.setStatus(401);
            throw new CustomerAuthenticationException(TokenConstant.TOKEN_EXPIRED);
        }

        LoginUser loginUser = null;
        //  校验token
        try {
            Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
            Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
            User user = userMapper.findById(userId);
            // 授权操作
            List<String> permission = menuMapper.selectPermissionByUserId(user.getId());
            loginUser = new LoginUser(user, permission);
        } catch (Exception e) {
            // 不通过,响应401状态码
            response.setStatus(401);
            throw new CustomerAuthenticationException(TokenConstant.TOKEN_INVALID);
        }
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}

controller

@RestController
@RequestMapping("/user")
@Slf4j
@Tag(name = "用户登录接口")
@RequiredArgsConstructor
public class LoginController {

    private final LoginService loginService;

    // redis
    private final RedisTemplate redisTemplate;

    // jwt配置
    private final JwtProperties jwtProperties;

    @Operation(summary = "用户登录")
    @PostMapping("/login")
    public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDto)
    {
        UserLoginVO userLoginVO = loginService.login(userLoginDto);
        return Result.success(userLoginVO);
    }

    @GetMapping("/hello")
    @PreAuthorize("hasAuthority('user:normal')")
    public Result<String> hello() {
        return Result.success("hello user!");
    }

    @GetMapping("/admin")
    @PreAuthorize("hasAuthority('user:manage')")
    public Result<String> admin() {
        return Result.success("hello admin!");
    }

    @PostMapping("/logout")
    @Operation(summary = "用户退出")
    public Result logout(HttpServletRequest request, HttpServletResponse response) {
        // 在logout中获取token
        String token = request.getHeader(jwtProperties.getUserTokenName());

        if (ObjectUtils.isEmpty(token)) {
            token = request.getParameter(jwtProperties.getUserTokenName());
        }

        if (ObjectUtils.isEmpty(token)) {
            throw new CustomerAuthenticationException(TokenConstant.TOKEN_IS_NULL);
        }

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication!=null) {
            // 清空用户信息
            new SecurityContextLogoutHandler().logout(request, response, authentication);
            // 清空redis里面的token
            redisTemplate.delete(RedisConstant.TOKEN_HEADER +token);
        }
        return Result.success(LoginConstant.LOGOUT_SUCCESS);
    }
}

domain

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;

    // 权限
    private List<String> permissions;


    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    public LoginUser(User user) {
        this.user = user;
    }

    // 自定义权限列表集合
    @JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (authorities != null) {
            return authorities;
        }
        authorities = new ArrayList<>();
        permissions.forEach(permission -> {
            authorities.add(new SimpleGrantedAuthority(permission));
        });
        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

service

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    private final MenuMapper menuMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 用户名为空
        if (username.equals("")) {
            throw new InternalAuthenticationServiceException(LoginConstant.USERNAME_IS_NULL);
        }

        // 根据用户名查询用户信息
        User user = userRepository.findByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException(LoginConstant.USER_NOT_FOUND);
        }

        // 授权操作
        List<String> permission = menuMapper.selectPermissionByUserId(user.getId());

        return new LoginUser(user,permission);
    }
}
@Service
@RequiredArgsConstructor
public class LoginServiceImpl implements LoginService {

    // redis配置
    private final RedisTemplate redisTemplate;

    private final JwtProperties jwtProperties;

    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 用户登录
     *
     * @param userLoginDto
     * @return
     */
    @Override
    public UserLoginVO login(UserLoginDTO userLoginDto) {

        // 封装Authentication对象
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userLoginDto.getUsername(), userLoginDto.getPassword());

        // 进行校验
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

        if (Objects.isNull(authenticate)) {
            throw new LoginFailedException(LoginConstant.LOGIN_FAILED);
        }

        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();

        User user = loginUser.getUser();

        // 登录成功后生成token
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.USER_ID, user.getId());

        String token = JwtUtil.createJWT(
                jwtProperties.getUserSecretKey(),
                jwtProperties.getUserTtl(),
                claims
        );

        // 将用户token设置到Redis中
        // token作为用户身份验证的凭证,需要在服务器端进行存储和管理
        redisTemplate.opsForValue().set(
                // 键名
                RedisConstant.TOKEN_HEADER + token,
                // 键值,此处键值相同,因为只需要存储token本身
                token,
                // 过期时间,使用JWT配置中的用户token过期时间
                jwtProperties.getUserTtl(),
                // 时间单位,指定过期时间为毫秒单位
                TimeUnit.MILLISECONDS
        );

        UserLoginVO userLoginVO = UserLoginVO.builder()
                .id(user.getId())
                .name(user.getName())
                .token(token)
                .username(user.getUsername())
                .build();
        return userLoginVO;
    }
}

核心配置

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    // 用户登录服务
    private final UserDetailsServiceImpl userDetailsService;

    // 用户认证校验失败处理器
    private final LoginFailureHandler loginFailureHandler;

    // 匿名用户无权限访问资源处理类
    private final AnonymousAuthenticationHandler anonymousAuthenticationHandler;

    // 认证用户无权限访问的处理器
    private final CustomerAccessDeniedHandler customerAccessDeniedHandler;

    // jwt过滤器
    private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    /**
     * 创建BCryptPasswordEncoder注入容器
     * 此容器会自动讲密码进行加密,同时会生成随机盐,会产生不同的密文
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 创建AuthenticationManager注入容器
     * 登录时需要调用AuthenticationManager.authenticate执行一次校验
     * @param config
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // 配置关闭csrf
                .csrf(csrf -> csrf.disable())
                // 支持跨域
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                // 配置请求拦截方式
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/user/login")
                        .permitAll()
                        .anyRequest()
                        .authenticated()
                )
                // 把token校验过滤器添加到过滤器链中
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                // 自定义异常处理
                .exceptionHandling(exception -> exception
                        // 认证用户无权限访问的处理器
                        .accessDeniedHandler(customerAccessDeniedHandler)
                        // 匿名用户访问无权限的处理器
                        .authenticationEntryPoint(anonymousAuthenticationHandler))
                // 用户认证校验失败处理器
                .formLogin(configurer -> configurer.failureHandler(loginFailureHandler))
                //不需要session
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        ;
        return http.build();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        // 提供自定义loadUserByUsername
        authProvider.setUserDetailsService(userDetailsService);
        // 指定密码编辑器
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    /**
     * 配置跨域
     * @return
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*")); // 允许的来源
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的方法
        configuration.setAllowedHeaders(Arrays.asList("*")); // 允许的头部
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration); // 应用于所有路径
        return source;
    }

}
@EnableJpaAuditing  // springJPA注解
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true) // springsecurity注解
public class Springboot3Application {
public static void main(String[] args) {
        org.springframework.boot.SpringApplication.run(Springboot3Application.class, args);
    }
}

好的,下面是Spring Boot 3 + Spring Security 6 + JWT项目配置步骤: 1. 添加Spring SecurityJWT的依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 配置JWT 在application.yml或者application.properties文件中添加JWT的配置信息: ``` jwt: secret: yourSecretKey expiration: 604800000 # 7 days ``` 3. 配置Spring Security 创建一个继承自WebSecurityConfigurerAdapter的配置类,并添加以下代码: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService customUserDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**") .permitAll() .anyRequest() .authenticated(); http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } } ``` 其中,CustomUserDetailsService是自定义的用户认证服务,JwtAuthenticationFilter是自定义的JWT认证过滤器。 4. 编写JWT认证过滤器 创建一个继承自OncePerRequestFilter的JwtAuthenticationFilter,并添加以下代码: ```java public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtTokenProvider jwtTokenProvider; @Autowired private CustomUserDetailsService customUserDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String token = jwtTokenProvider.resolveToken(request); if (token != null && jwtTokenProvider.validateToken(token)) { Authentication authentication = jwtTokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (JwtAuthenticationException ex) { SecurityContextHolder.clearContext(); response.sendError(ex.getHttpStatus().value(), ex.getMessage()); return; } filterChain.doFilter(request, response); } } ``` 其中,JwtTokenProvider是自定义的JWT Token提供器。在这个过滤器中,我们通过JWT Token提供器解析请求中的Token,并将用户认证信息存储在SecurityContextHolder中。 5. 编写JWT Token提供器 创建一个JwtTokenProvider类,并添加以下代码: ```java @Service public class JwtTokenProvider { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String createToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secret).parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException ex) { throw new JwtAuthenticationException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR); } } public Authentication getAuthentication(String token) { UserDetails userDetails = customUserDetailsService.loadUserByUsername(getUsername(token)); return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); } public String getUsername(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody() .getSubject(); } public String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } } ``` 其中,UserDetails是Spring Security提供的用户认证信息对象,CustomUserDetailsService是自定义的用户认证服务。 在这个类中,我们使用JJWT库来创建和解析JWT Token,并在getAuthentication方法中从Token中获取用户认证信息,并将其封装成Spring Security的Authentication对象。 以上就是Spring Boot 3 + Spring Security 6 + JWT项目配置步骤。希望能够帮到您!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值