如何用Spring Security实现临时令牌30分钟限时访问的REST API?
实现方案
针对你的需求,我们可以在现有Spring Security + JWT的基础上,通过新增临时令牌认证流程来实现/api/v1/statement/**路径的30分钟限时访问,具体步骤如下:
方案一:基于Redis存储的临时令牌(支持主动失效)
这种方案用Redis存储临时令牌与用户的关联关系,自带过期时间管理,还能主动取消令牌权限。
1. 生成临时令牌接口(需原有JWT认证)
先提供一个接口,让已通过JWT认证的用户获取临时令牌:
@RestController @RequestMapping("/api/v1/tokens") public class TempTokenController { @Autowired private RedisTemplate<String, String> redisTemplate; @PostMapping("/temp") public ResponseEntity<String> generateTempToken(Authentication authentication) { // 从当前已认证的用户信息中获取用户ID String userId = authentication.getName(); // 生成唯一临时令牌(用UUID保证唯一性) String tempToken = UUID.randomUUID().toString(); // 存入Redis,设置30分钟过期 redisTemplate.opsForValue() .set("temp_token:" + tempToken, userId, 30, TimeUnit.MINUTES); return ResponseEntity.ok(tempToken); } }
2. 自定义临时令牌认证过滤器
编写过滤器,专门处理/api/v1/statement/**路径的请求,验证临时令牌的有效性:
@Component public class TempTokenAuthenticationFilter extends OncePerRequestFilter { @Autowired private RedisTemplate<String, String> redisTemplate; // 自定义临时令牌的请求头名称 private static final String TEMP_TOKEN_HEADER = "X-Temp-Token"; private static final String STATEMENT_PATH_PREFIX = "/api/v1/statement/"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestUri = request.getRequestURI(); // 仅处理statement路径的请求 if (requestUri.startsWith(STATEMENT_PATH_PREFIX)) { String tempToken = request.getHeader(TEMP_TOKEN_HEADER); if (tempToken == null || tempToken.isBlank()) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "临时令牌缺失"); return; } // 从Redis验证令牌是否有效、未过期 String userId = redisTemplate.opsForValue().get("temp_token:" + tempToken); if (userId == null) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "临时令牌无效或已过期"); return; } // 构建认证上下文,这里可根据需求加载用户权限 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userId, null, Collections.emptyList()); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } filterChain.doFilter(request, response); } }
3. 配置Spring Security过滤器顺序
在原有Security配置中,添加临时令牌过滤器,确保它在JWT过滤器之后执行(仅处理statement路径,不影响其他接口的JWT认证):
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; // 原有JWT过滤器 @Autowired private TempTokenAuthenticationFilter tempTokenAuthenticationFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth // 生成临时令牌的接口需要原有JWT认证 .requestMatchers("/api/v1/tokens/temp").authenticated() // statement路径需临时令牌认证 .requestMatchers("/api/v1/statement/**").authenticated() // 其他路径保持原有JWT认证逻辑 .anyRequest().authenticated()) // 先执行原有JWT过滤器,再执行临时令牌过滤器 .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .addFilterAfter(tempTokenAuthenticationFilter, JwtAuthenticationFilter.class); return http.build(); } // 原有JWT相关配置(如AuthenticationManager、JwtTokenProvider等)保持不变 }
方案二:临时JWT令牌(无需额外存储)
如果不想引入Redis,可以直接生成一个短有效期的JWT作为临时令牌,逻辑和原有JWT类似,只是过期时间设为30分钟:
1. 新增临时JWT生成方法
在原有JWT工具类中添加生成临时令牌的方法:
@Component public class JwtTokenProvider { // 原有JWT密钥、过期时间配置 @Value("${jwt.secret}") private String secret; // 生成临时JWT(30分钟过期) public String generateTempToken(String userId) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + 30 * 60 * 1000); // 30分钟 return Jwts.builder() .setSubject(userId) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } }
2. 生成临时令牌接口
和方案一类似,通过JWT认证后获取临时JWT:
@RestController @RequestMapping("/api/v1/tokens") public class TempTokenController { @Autowired private JwtTokenProvider jwtTokenProvider; @PostMapping("/temp") public ResponseEntity<String> generateTempToken(Authentication authentication) { String userId = authentication.getName(); String tempJwt = jwtTokenProvider.generateTempToken(userId); return ResponseEntity.ok(tempJwt); } }
3. 自定义临时JWT过滤器
编写过滤器解析临时JWT,仅处理statement路径:
@Component public class TempJwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtTokenProvider jwtTokenProvider; private static final String TEMP_JWT_HEADER = "X-Temp-Jwt"; private static final String STATEMENT_PATH_PREFIX = "/api/v1/statement/"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestUri = request.getRequestURI(); if (requestUri.startsWith(STATEMENT_PATH_PREFIX)) { String tempJwt = request.getHeader(TEMP_JWT_HEADER); if (tempJwt == null || tempJwt.isBlank()) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "临时JWT缺失"); return; } try { // 解析并验证临时JWT String userId = jwtTokenProvider.getUserIdFromJwt(tempJwt); if (!jwtTokenProvider.validateToken(tempJwt)) { throw new RuntimeException("临时JWT无效"); } // 构建认证上下文 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userId, null, Collections.emptyList()); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } catch (Exception ex) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "临时JWT无效或已过期"); return; } } filterChain.doFilter(request, response); } }
4. Spring Security配置
和方案一一致,将临时JWT过滤器添加到过滤器链中即可。
关键注意事项
- 生成临时令牌的接口必须由原有JWT认证保护,确保只有合法用户才能获取临时权限。
- 若需要对
/api/v1/statement/**接口做更细粒度的权限控制,可以在构建认证上下文时添加对应的权限集合,配合@PreAuthorize注解使用。 - 方案一的Redis存储方案支持主动失效临时令牌(比如用户主动取消权限时,删除Redis中对应的key),而方案二的临时JWT只能等待自然过期,可根据业务需求选择。
内容的提问来源于stack exchange,提问作者Sumit Mishra
相关产品推荐
相关产品推荐

