You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 00:40:18