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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 06:33:26