Spring Boot如何在每次请求时为JWT Token添加自定义验证?
实现JWT Token的IssuedAt与数据库SessionID前置验证
核心思路
要通过@PreAuthorize实现自定义验证且避免重复解析JWT,关键是在认证阶段一次性解析Token并缓存关键信息,后续验证逻辑直接复用缓存数据,无需二次解析Token。
步骤1:改造JWT认证过滤器,缓存IssuedAt信息
在JWT认证过滤器中,解析Token后将issuedAt存入Authentication对象的details字段,后续验证直接从这里读取:
public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = extractTokenFromRequest(request); if (token != null && jwtTokenProvider.validateToken(token)) { // 解析Token获取用户信息和issuedAt String username = jwtTokenProvider.getUsernameFromToken(token); String tokenIssuedAt = jwtTokenProvider.getIssuedAtDateFromToken(token); // 加载用户详情 UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 构造Authentication,将issuedAt存入details UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new JwtTokenDetails(tokenIssuedAt)); // 存入SecurityContext供后续验证使用 SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } // 辅助类:存储JWT额外信息 private static class JwtTokenDetails { private final String issuedAt; public JwtTokenDetails(String issuedAt) { this.issuedAt = issuedAt; } public String getIssuedAt() { return issuedAt; } } // 从请求头/参数中提取Token的方法,根据你的实际实现调整 private String extractTokenFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (bearerToken != null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }
步骤2:自定义Security验证方法
创建组件类,编写验证逻辑,从SecurityContext中取出缓存的issuedAt,与数据库中的sessionID对比:
@Component("customSecurity") public class CustomSecurityExpressions { private final UserSessionService userSessionService; // 负责从数据库查询用户最新sessionID public CustomSecurityExpressions(UserSessionService userSessionService) { this.userSessionService = userSessionService; } public boolean isValidTokenIssuedAt() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !(authentication.getDetails() instanceof JwtTokenDetails)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Invalid authentication"); } JwtTokenDetails tokenDetails = (JwtTokenDetails) authentication.getDetails(); String tokenIssuedAt = tokenDetails.getIssuedAt(); String username = authentication.getName(); // 从数据库获取用户最新的sessionID String sessionID = userSessionService.getLatestSessionIdByUsername(username); if (!tokenIssuedAt.equals(sessionID)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Session not up-to-date!!"); } return true; } }
步骤3:在接口上应用自定义验证
修改原接口的@PreAuthorize注解,叠加权限校验与自定义验证逻辑:
@PostMapping("/add") @PreAuthorize("hasAuthority('user_add') && @customSecurity.isValidTokenIssuedAt()") public void addUser(@RequestBody User user, HttpServletRequest request) { userService.addNewUser(user, request); }
关键说明
- 避免重复解析:Token仅在过滤器中解析一次,
issuedAt被缓存到SecurityContext,后续验证直接读取,无需重复调用解析方法。 - 双重校验:
@PreAuthorize同时保留原有权限校验hasAuthority('user_add')和自定义的isValidTokenIssuedAt()验证,两者都通过才允许访问接口。 - 异常处理:验证不通过时直接抛出
ResponseStatusException,Spring会自动转换为HTTP 403响应。
内容的提问来源于stack exchange,提问作者Ahmed Salah
相关产品推荐
相关产品推荐

