Spring Boot中能否同时用OAuth2与API Key实现双认证?
Spring Boot同时支持OAuth2 JWT与API Key认证解决方案
可以同时支持两种认证方式,但直接用addFilterBefore简单添加API Key过滤器容易出现类型转换错误,需要正确配置Spring Security的认证流程,让两种认证逻辑共存且互不干扰。
问题原因分析
你遇到的APIKeyAuthenticationToken cannot be cast to JwtAuthenticationToken错误,是因为代码中存在强制将认证对象转换为JwtAuthenticationToken的逻辑(比如全局拦截器、控制器方法里),但当通过API Key认证时,当前认证对象是自定义的APIKeyAuthenticationToken,类型不匹配导致报错。同时,直接添加过滤器可能没有正确整合到Spring Security的认证管理器中,导致认证流程冲突。
实现步骤
1. 自定义API Key相关认证组件
1.1 定义API Key认证Token
public class APIKeyAuthenticationToken extends AbstractAuthenticationToken { private final String apiKey; // 未认证的Token public APIKeyAuthenticationToken(String apiKey) { super(Collections.emptyList()); this.apiKey = apiKey; setAuthenticated(false); } // 已认证的Token public APIKeyAuthenticationToken(String apiKey, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.apiKey = apiKey; super.setAuthenticated(true); } @Override public Object getCredentials() { return apiKey; } @Override public Object getPrincipal() { return apiKey; } }
1.2 实现API Key认证过滤器
负责从请求中提取API Key(这里从X-API-Key头部提取,也可改为请求参数),并提交给认证管理器处理:
public class APIKeyAuthenticationFilter extends OncePerRequestFilter { private static final String API_KEY_HEADER = "X-API-Key"; 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 apiKey = request.getHeader(API_KEY_HEADER); if (apiKey != null && !apiKey.isBlank()) { APIKeyAuthenticationToken authToken = new APIKeyAuthenticationToken(apiKey); // 交给认证管理器验证API Key有效性 Authentication authenticated = authenticationManager.authenticate(authToken); // 将认证结果存入安全上下文 SecurityContextHolder.getContext().setAuthentication(authenticated); } filterChain.doFilter(request, response); } }
1.3 实现API Key认证提供者
负责验证API Key的合法性,这里从配置文件读取允许的API Key,也可从数据库查询:
@Component public class APIKeyAuthenticationProvider implements AuthenticationProvider { @Value("${app.api-keys.allowed}") private List<String> allowedApiKeys; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String apiKey = (String) authentication.getCredentials(); if (allowedApiKeys.contains(apiKey)) { // 为API Key分配对应权限 List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_API_CLIENT")); return new APIKeyAuthenticationToken(apiKey, authorities); } throw new BadCredentialsException("无效的API Key"); } @Override public boolean supports(Class<?> authentication) { // 只处理APIKeyAuthenticationToken类型的认证请求 return APIKeyAuthenticationToken.class.isAssignableFrom(authentication); } }
2. 配置SecurityFilterChain,整合两种认证方式
将API Key认证组件注册到Spring Security的认证流程中,同时保留OAuth2资源服务器配置:
@Configuration @EnableWebSecurity public class SecurityConfig { private final APIKeyAuthenticationProvider apiKeyAuthenticationProvider; public SecurityConfig(APIKeyAuthenticationProvider apiKeyAuthenticationProvider) { this.apiKeyAuthenticationProvider = apiKeyAuthenticationProvider; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeRequests(auth -> auth // 配置特定路径仅允许API Key客户端访问 .antMatchers("/api/thirdparty/**").hasAuthority("ROLE_API_CLIENT") // 其他接口需要认证(两种方式都可) .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(Customizer.withDefaults()) // 保留原有JWT配置 ) // 将API Key过滤器添加到用户名密码认证过滤器之前 .addFilterBefore(apiKeyAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { ProviderManager manager = (ProviderManager) authConfig.getAuthenticationManager(); // 将API Key认证提供者加入认证管理器,与JWT认证提供者共存 manager.getProviders().add(apiKeyAuthenticationProvider); return manager; } @Bean public APIKeyAuthenticationFilter apiKeyAuthenticationFilter() throws Exception { return new APIKeyAuthenticationFilter(authenticationManager(null)); } }
3. 解决类型转换错误
如果代码中存在强制转换JwtAuthenticationToken的逻辑,需要修改为判断类型后处理:
Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth instanceof JwtAuthenticationToken) { JwtAuthenticationToken jwtToken = (JwtAuthenticationToken) auth; // 处理JWT相关逻辑,比如获取用户信息 } else if (auth instanceof APIKeyAuthenticationToken) { APIKeyAuthenticationToken apiKeyToken = (APIKeyAuthenticationToken) auth; // 处理API Key相关逻辑 }
或者通过路径隔离,让需要JWT信息的接口仅接受OAuth2认证,避免类型转换问题。
配置文件补充
在application.yml中添加API Key配置:
app: api-keys: allowed: ["api-key-123", "api-key-456"] # 允许的API Key列表
内容的提问来源于stack exchange,提问作者iPhoneJavaDev
相关产品推荐
相关产品推荐

