遂宁市网站建设_网站建设公司_页面权重_seo优化
2025/12/24 18:25:28 网站建设 项目流程

深入解析:基于Spring Boot 3 + Spring Security6 + JWT + Redis实现登录、token身份认证

2025-12-24 18:23  tlnshuju  阅读(0)  评论(0)    收藏  举报

基于Spring Boot3实现Spring Security6 + JWT + Redis实现登录、token身份认证。

  • 用户从数据库中获取。
  • 使用RESTFul风格的APi进行登录。
  • 使用JWT生成token。
  • 使用Redis进行登录过期判断。
  • 所有的工具类和数据结构在源码中都有。

系列文章指路??
系列文章-基于SpringBoot3创建项目并配置常用的工具和一些常用的类

项目源码??
/shijizhe/boot-test

文章目录

依赖版本

  • Spring Boot 3.0.6
  • Spring Security 6.0.3

原理

这张图大家已经估计已经看过很多次了。
原理
实现登录认证的过程,其实就是对上述的类按照自己的需求进行自定义扩展的过程。具体不多讲了,别的文章里讲得比我透彻。

show you my code.

代码结构

security 配置

在这里插入图片描述

用户登录、注册controller,用户服务

在这里插入图片描述

用到的工具类

在这里插入图片描述

注册 AuthController.register

将用户密码使用BCrypt加密存储。

    @PostMapping("/register")@Operation(summary = "register", description = "用户注册")public Object register(@RequestBody @Valid UserRegisterDTO userRegisterDTO) {YaUser userById = userService.getUserById(userRegisterDTO.getUserId());if(Objects.nonNull(userById)){return BaseResult.fail("用户id已存在");}try {BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();YaUser yaUser = UserRegisterMapper.INSTANCE.registerToUser(userRegisterDTO);yaUser.setUserPassword(encoder.encode(userRegisterDTO.getUserPassword()));userService.insertUser(yaUser);return BaseResult.success("用户注册成功");}catch (Exception e){return BaseResult.fail("用户注册过程中遇到异常:" + e);}}

登录

1.登录API:AuthController.login

我们使用RESTFul风格的API来代替表单进行登录。这个接口只是提供一个Swagger调用登录接口的入口,实际逻辑由Filter控制。
在这里插入图片描述

2. 登录过滤器:继承UsernamePasswordAuthenticationFilter

拦截指定的登录请求,交给AuthenticationProvider处理。对Provider返回的登录结果进行处理。

  • 通过指定filterProcessesUrl,指定登录接口的路径。

  • 登录失败,将异常信息返回前端。

  • 登录成功,通过JwtUtils生成token,放入响应header中。并将token用户信息(json字符串)存入Redis中。

  • 通过JwtUtils生成token设置为永不过期,存入Redis的token过期时间设置为30分钟,以便后边做登录过期的判断。

    /**

    • 拦截登陆过滤器

    • @author Ya Shi

    • @since 2024/3/21 16:20
      */
      @Slf4j
      public class YaLoginFilter extends UsernamePasswordAuthenticationFilter {
      private final RedisUtils redisUtils;

      private final Long expiration;

      public YaLoginFilter(AuthenticationManager authenticationManager, RedisUtils redisUtils, Long expiration) {
      this.expiration = expiration;
      this.redisUtils = redisUtils;
      super.setAuthenticationManager(authenticationManager);
      super.setPostOnly(true);
      super.setFilterProcessesUrl(“/auth/login”);
      super.setUsernameParameter(“userId”);
      super.setPasswordParameter(“userPassword”);
      }

      @SneakyThrows
      @Override
      public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
      log.info(“YaLoginFilter authentication start”);
      // 数据是通过 RequestBody 传输
      UserLoginDTO user = JSON.parseObject(request.getInputStream(), StandardCharsets.UTF_8, UserLoginDTO.class);

       return super.getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUserId(), user.getUserPassword()));

      }

      @Override
      protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
      FilterChain chain,
      Authentication authResult) {
      log.info(“YaLoginFilter authentication success: {}”, authResult);
      // 如果验证成功, 就生成Token并返回
      UserDetails userDetails = (UserDetails) authResult.getPrincipal();
      String userId = userDetails.getUsername();
      String token = JwtUtils.generateToken(userId);
      response.setHeader(TOKEN_HEADER, TOKEN_PREFIX + token);
      // 将token存入Redis中
      redisUtils.set(REDIS_KEY_AUTH_TOKEN + userId, token, expiration);
      log.info(“YaLoginFilter authentication end”);
      // 将UserDetails存入redis中
      redisUtils.set(REDIS_KEY_AUTH_USER_DETAIL + userId, JSON.toJSONString(userDetails), 1, TimeUnit.DAYS);

       ServletUtils.renderResult(response, new BaseResult<>(ResultEnum.SUCCESS.code, "登陆成功"));log.info("YaLoginFilter authentication end");

      }

      /**

      • 如果 attemptAuthentication 抛出 AuthenticationException 则会调用这个方法
        */
        @Override
        protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException failed) throws IOException {
        log.info(“YaLoginFilter authentication failed: {}”, failed.getMessage());
        ServletUtils.renderResult(response, new BaseResult<>(ResultEnum.FAILED_UNAUTHORIZED.code, “登陆失败:” + failed.getMessage()));
        }
3.身份认证:实现AuthenticationProvider

调用UserDetailsService查询用户的账户、权限信息与登录接口输入的账户、密码对比。认证通过则返回用户信息。

/*** 

* 自定义认证*

** @author Ya Shi* @since 2024/3/21 15:00*/ @Component public class YaAuthenticationProvider implements AuthenticationProvider {@AutowiredYaUserDetailService userDetailService;@AutowiredPasswordEncoder passwordEncoder;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {// 获取用户输入的用户名和密码String username = authentication.getName();String password = authentication.getCredentials().toString();UserDetails userDetails = userDetailService.loadUserByUsername(username);boolean matches = passwordEncoder.matches(password, userDetails.getPassword());if(!matches){throw new AuthenticationException("User password error."){};}return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());}@Overridepublic boolean supports(Class authentication) {return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);} }
4.从数据库中查询用户信息:实现UserDetailsService

从数据库中查询出用户的信息,供AuthenticationProvider登录认证时使用。

  • 用户权限这块,目前还没用到,可以忽略。用户鉴权可能后边会单独补上。

  • 为什么这里没先从Redis取用户信息?

    1. 如果权限或者用户信息变更这里取不到
    2. Redis里不建议存储用户密码。

    /**

    • 继承UserDetailsService,实现自定义登陆认证

    • @author Ya Shi

    • @since 2024/3/19 11:32
      */
      @Service
      public class YaUserDetailService implements UserDetailsService {

      @Autowired
      UserService userService;
      @Override
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      YaUser user = userService.getUserById(username);
      if(Objects.isNull(user)){
      throw new UsernameNotFoundException(“User not Found.”);
      }
      List roles = userService.listRoleById(username);
      List authorities = new ArrayList<>(roles.size());
      roles.forEach( role -> authorities.add(new SimpleGrantedAuthority(role.getRoleId())));

       return new User(username, user.getUserPassword(), authorities);

      }
      }

5. Security配置: 使用注解@EnableWebSecurity

token身份认证

1. token身份认证过滤器: OncePerRequestFilter

UserAuthUtils

已经登录的用户,可以从Security的上下文中获取用户的账号、基本信息、权限等。可以将其封装为工具类。因为练手的用户表较为简单,也没有部分、员工、角色、权限等概念,因此仅封装了getUserId做抛砖引玉的作用。可以根据实际使用自己封装更多的方法。

getUserId
public static String getUserId() {if (Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {return null;}UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();if (Objects.isNull(userDetails)) {return null;}return userDetails.getUsername();}

用户登出

JWT本身是无状态的,但是我们后端将jwt存到redis里,相当于手动使JWT变得有状态了。那么我们在登出时就需要清空Redis中的jwt。

实现LogoutSuccessHandler
/*** 

* 登出成功*

** @author Ya Shi* @since 2024/3/28 10:47*/ @Slf4j public class YaLogoutSuccessHandler implements LogoutSuccessHandler {private final RedisUtils redisUtils;public YaLogoutSuccessHandler(RedisUtils redisUtils) {this.redisUtils = redisUtils;}@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {final String authorization = request.getHeader(AuthConstants.TOKEN_HEADER);// 1.请求头中没有携带tokenif (StrUtil.isBlank(authorization)) {ServletUtils.renderResult(response, BaseResult.successWithMessage("没有登录信息,无需退出"));return;}// 携带tokenfinal String token = authorization.replace(AuthConstants.TOKEN_PREFIX, "");String userId;// 2.提供的token异常try {userId = JwtUtils.extractUserId(token);}catch (Exception e){log.error("YaLogoutHandler logout 解析jwt异常:{}", e.toString());ServletUtils.renderResult(response, new BaseResult<>(ResultEnum.FAILED_UNAUTHORIZED.code, "凭证异常"));return;}// 清空RedisredisUtils.delete(REDIS_KEY_AUTH_TOKEN + userId);log.info("YaLogoutSuccessHandler onLogoutSuccess");ServletUtils.renderResult(response, BaseResult.successWithMessage("退出登录成功"));} }
修改Security配置 : YaSecurityConfig
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http  ... // 前面的配置忽略.logout().logoutUrl("/auth/logout").logoutSuccessHandler(new YaLogoutSuccessHandler(redisUtils));return http.build();
}

下一步的计划

  • 用户鉴权
  • 排查permitAll()失效的问题。
  • 做一个练手用的用户中心,提供统一的注册、登录、认证、鉴权服务,供其他的应用调用。
  • 把前期已经实现的基础的配置和工具类封装为jar包,供以后的程序使用。

参考文章

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询