如何配置Spring同时支持Cognito JWT与自定义API令牌授权?
同时支持Cognito JWT与自定义API令牌的Spring Security配置
核心思路
Spring Security支持配置多套认证逻辑,你可以在保留现有Cognito JWT认证流程的基础上,新增自定义API令牌的认证机制,两者互不干扰,请求会自动匹配有效的认证方式。
步骤1:保留原有Cognito JWT配置
继续维持你已实现的Cognito JWT认证逻辑(比如基于spring-security-oauth2-resource-server的JWT解码器配置),这部分无需修改,确保终端用户的OAuth认证流程正常运行。
步骤2:实现自定义API令牌的认证逻辑
2.1 定义令牌存储模型
先创建实体类存储自定义API令牌的核心信息:
@Entity public class ApiToken { @Id private String token; private String consumerId; // 标识令牌所属的外部消费者 private Instant expiresAt; private Set<String> authorities; // 令牌拥有的权限集合 // getter、setter、构造方法 }
对应的数据库操作Repository:
public interface ApiTokenRepository extends JpaRepository<ApiToken, String> { Optional<ApiToken> findByTokenAndExpiresAtAfter(String token, Instant now); }
2.2 自定义AuthenticationToken
创建自定义认证令牌类,封装令牌的认证状态和信息:
public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken { private final String token; private final Object principal; // 未认证状态的构造方法 public ApiKeyAuthenticationToken(String token) { super(null); this.token = token; this.principal = null; setAuthenticated(false); } // 已认证状态的构造方法 public ApiKeyAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.token = null; this.principal = principal; setAuthenticated(true); } @Override public Object getCredentials() { return token; } @Override public Object getPrincipal() { return principal; } }
2.3 自定义AuthenticationProvider
实现认证提供者,负责验证自定义令牌的有效性:
@Component public class ApiKeyAuthenticationProvider implements AuthenticationProvider { private final ApiTokenRepository tokenRepository; public ApiKeyAuthenticationProvider(ApiTokenRepository tokenRepository) { this.tokenRepository = tokenRepository; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String token = (String) authentication.getCredentials(); ApiToken apiToken = tokenRepository.findByTokenAndExpiresAtAfter(token, Instant.now()) .orElseThrow(() -> new BadCredentialsException("无效或已过期的API令牌")); // 转换权限为Spring Security标准格式 Collection<GrantedAuthority> authorities = apiToken.getAuthorities().stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); return new ApiKeyAuthenticationToken(apiToken.getConsumerId(), authorities); } @Override public boolean supports(Class<?> authentication) { return ApiKeyAuthenticationToken.class.isAssignableFrom(authentication); } }
2.4 自定义认证过滤器
创建过滤器,从请求头中提取自定义令牌并触发认证流程:
public class ApiKeyAuthenticationFilter extends OncePerRequestFilter { private static final String API_TOKEN_HEADER = "X-API-Token"; private final AuthenticationManager authenticationManager; public ApiKeyAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader(API_TOKEN_HEADER); if (token != null && !token.isBlank()) { Authentication authRequest = new ApiKeyAuthenticationToken(token); Authentication authResult = authenticationManager.authenticate(authRequest); SecurityContextHolder.getContext().setAuthentication(authResult); } filterChain.doFilter(request, response); } }
步骤3:整合两种认证方式到Spring Security
修改Security配置类,添加自定义过滤器和认证提供者,同时保留Cognito的JWT配置:
@Configuration @EnableWebSecurity public class SecurityConfig { private final ApiKeyAuthenticationProvider apiKeyAuthenticationProvider; private final JwtDecoder jwtDecoder; // 原有Cognito JWT解码器 public SecurityConfig(ApiKeyAuthenticationProvider apiKeyAuthenticationProvider, JwtDecoder jwtDecoder) { this.apiKeyAuthenticationProvider = apiKeyAuthenticationProvider; this.jwtDecoder = jwtDecoder; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) // 保留原有Cognito JWT认证配置 .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.decoder(jwtDecoder)) ) // 添加自定义API令牌过滤器,放在JWT过滤器之前优先检查 .addFilterBefore(apiKeyAuthenticationFilter(), BearerTokenAuthenticationFilter.class); return http.build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { ProviderManager manager = (ProviderManager) config.getAuthenticationManager(); // 注册自定义认证提供者 manager.getProviders().add(apiKeyAuthenticationProvider); return manager; } @Bean public ApiKeyAuthenticationFilter apiKeyAuthenticationFilter() throws Exception { return new ApiKeyAuthenticationFilter(authenticationManager(null)); } }
步骤4:实现令牌的颁发与撤销接口
创建管理员专用接口,手动生成和删除API令牌:
@RestController @RequestMapping("/api/token") public class ApiTokenController { private final ApiTokenRepository tokenRepository; private final SecureRandom random = new SecureRandom(); public ApiTokenController(ApiTokenRepository tokenRepository) { this.tokenRepository = tokenRepository; } // 颁发令牌,仅管理员可调用 @PostMapping("/issue") @PreAuthorize("hasRole('ADMIN')") public ApiToken issueToken(@RequestBody TokenRequest request) { // 生成32位随机令牌字符串 byte[] tokenBytes = new byte[16]; random.nextBytes(tokenBytes); String token = Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes); ApiToken apiToken = new ApiToken(); apiToken.setToken(token); apiToken.setConsumerId(request.getConsumerId()); apiToken.setExpiresAt(Instant.now().plus(Duration.ofDays(request.getExpireDays()))); apiToken.setAuthorities(request.getAuthorities()); return tokenRepository.save(apiToken); } // 撤销令牌,仅管理员可调用 @DeleteMapping("/revoke/{token}") @PreAuthorize("hasRole('ADMIN')") public void revokeToken(@PathVariable String token) { tokenRepository.deleteById(token); } public static class TokenRequest { private String consumerId; private int expireDays; private Set<String> authorities; // getter、setter } }
注意事项
- 过滤器顺序:如果希望优先使用自定义API令牌,就把自定义过滤器放在
BearerTokenAuthenticationFilter之前;反之则调整顺序。 - 令牌存储:生产环境建议用Redis存储令牌,提升查询性能,同时利用Redis的过期自动清理功能。
- 权限管控:颁发/撤销令牌的接口必须严格限制权限,仅允许管理员操作。
- 令牌安全:生成令牌时使用足够随机的算法,传输过程强制使用HTTPS。
内容的提问来源于stack exchange,提问作者David Milne
相关产品推荐
相关产品推荐

