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

在使用AAD JWT的Spring Boot项目中添加第二种认证方案的实现咨询

在使用AAD JWT的Spring Boot项目中添加第二种认证方案的实现咨询

我的问题背景

我现在在Spring Boot项目里用了Spring Boot Active Directory Starter,目前只配置了OAuth2资源服务器——用JWKS URI来验证JWT,还加了个自定义的认证转换器。现在想给项目再加第二种认证方式,比如API Key认证,但完全不知道从哪下手。是直接加个新的认证提供商就行?还是得先给现有的AAD认证做些前置配置?如果要前置配置的话,能不能给我讲明白点?

我当前的Spring Security配置代码

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SpringSecurityConfig implements WebMvcConfigurer {

    @Value("${security.oauth2.resource.jwt.key-uri}")
    private String keySetUri;

    @Value("${security.oauth2.resource.id}")
    private String resourceId;

    @Value("${security.oauth2.issuer}")
    private String issuer;

    @Value("${security.oauth2.scope.access-as-user}")
    private String accessAsUserScope;

    private final FigaroAuthenticationConverter authenticationConverter;

    @Autowired
    public SpringSecurityConfig(FigaroAuthenticationConverter authenticationConverter) {
        this.authenticationConverter = authenticationConverter;
    }

    /**
     * Configures the security filter chain for the application.
     *
     * @param http the HttpSecurity to modify
     * @return the configured SecurityFilterChain
     * @throws Exception if an error occurs while configuring the filter chain
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.cors(corsCustomizer ->
                corsCustomizer.configurationSource(request -> {

                    CorsConfiguration config = new CorsConfiguration();

                    config.setAllowedOrigins(List.of("http://localhost:4200"));
                    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
                    config.setAllowCredentials(true);
                    config.setExposedHeaders(List.of("Authorization"));
                    config.setAllowedHeaders(List.of(
                            "Access-Control-Allow-Headers",
                            "Access-Control-Allow-Origin",
                            "Access-Control-Request-Method",
                            "Access-Control-Request-Headers",
                            "Origin",
                            "Cache-Control",
                            "Content-Type",
                            "Authorization"
                    ));

                    config.setMaxAge(3600L);

                    return config;
                }));

        http.sessionManagement(Customizer.withDefaults())
                .sessionManagement(sessionManagementConfigurer ->
                        sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));


        http.oauth2ResourceServer(oauth2 ->
                oauth2.jwt(jwtConfigurer -> {

                    jwtConfigurer.jwkSetUri(this.keySetUri);
                    jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter());

                }));

        http.authorizeHttpRequests(auth -> {
            auth.requestMatchers(HttpMethod.OPTIONS)
                    .permitAll();
            auth.requestMatchers("/actuator/**", "/api-docs").permitAll();
            auth.anyRequest().authenticated();
        });

        return http.build();
    }

    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(this.authenticationConverter);
        return jwtAuthenticationConverter;
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri(this.keySetUri).build();
    }
}

我尝试过的方法及遇到的问题

我之前查了点资料,看到说可以像下面这样加认证提供商,但还没实际测试:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(oAuth2ResourceServerConfigurer);
        auth.authenticationProvider(customAuthenticationProvider);
}

后来我试了写一个请求过滤器,想在官方认证过滤器之前执行,通过API Key认证后就跳过后续认证,但结果不符合预期:我设置完认证后直接return,虽然接口返回了200状态码,但完全没有响应体——应该是直接return导致过滤器链没走完,控制器根本没机会处理请求返回内容?

我的测试用过滤器代码如下:

public class ApiKeyAuthenticationFilter extends OncePerRequestFilter {

    private static final String HEADER_NAME = "X-API-KEY";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        logger.info("Checking for API key");

        String apiKey = request.getHeader(HEADER_NAME);
        
        logger.info(apiKey);

        if (StringUtils.hasText(apiKey)) {

            Authentication auth = new ApiKeyAuthenticationToken(apiKey, List.of(new SimpleGrantedAuthority("ROLE_VIEWER")));
            SecurityContextHolder.getContext().setAuthentication(auth);

            return;

        }

        filterChain.doFilter(request, response);

    }
}

目前这个过滤器只是测试用的,还没加API Key的合法性校验逻辑。


针对这个问题的解决方案

你用的是Spring Security 5.7+的SecurityFilterChain Bean配置方式,要支持多种认证方式,不需要去重写旧版的configure(AuthenticationManagerBuilder),而是可以通过注册多个认证提供商+配置过滤器链的方式来实现,下面给你详细讲步骤:

方案一:基于Spring Security标准流程实现多认证

1. 先实现自定义认证的核心组件

首先要定义API Key对应的认证Token,以及处理认证逻辑的提供商:

// 1. 自定义API Key认证Token,继承AbstractAuthenticationToken简化实现
public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken {

    private final String apiKey;

    // 未认证状态的构造器(用于过滤器提取凭证后提交给认证管理器)
    public ApiKeyAuthenticationToken(String apiKey) {
        super(Collections.emptyList());
        this.apiKey = apiKey;
        setAuthenticated(false);
    }

    // 已认证状态的构造器(认证提供商校验通过后返回)
    public ApiKeyAuthenticationToken(String apiKey, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.apiKey = apiKey;
        setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return apiKey; // API Key作为凭证返回
    }

    @Override
    public Object getPrincipal() {
        return apiKey; // 这里可以根据需求返回用户信息,比如对应的用户名
    }
}

// 2. 实现API Key认证提供商
@Component
public class ApiKeyAuthenticationProvider implements AuthenticationProvider {

    @Value("${security.api-key.valid-key}")
    private String validApiKey; // 从配置文件读取合法的API Key

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        ApiKeyAuthenticationToken token = (ApiKeyAuthenticationToken) authentication;
        String providedApiKey = token.getPrincipal().toString();

        // 校验API Key合法性
        if (validApiKey.equals(providedApiKey)) {
            // 给合法的API Key分配权限
            List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ROLE_API_USER"));
            return new ApiKeyAuthenticationToken(providedApiKey, authorities);
        }

        throw new BadCredentialsException("Invalid or missing API Key");
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return ApiKeyAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

2. 修复你的过滤器问题,并注册到过滤器链

你之前的过滤器错误在于直接return中断了过滤器链,正确的做法是设置完认证后必须继续执行过滤器链,这样后续的Spring MVC控制器才能处理请求并返回响应体。另外推荐通过AuthenticationManager来处理认证逻辑,更符合Spring Security的标准流程:

@Component
public class ApiKeyAuthenticationFilter extends OncePerRequestFilter {

    private static final String HEADER_NAME = "X-API-KEY";
    private static final Logger logger = LoggerFactory.getLogger(ApiKeyAuthenticationFilter.class);

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        logger.info("Checking for API key in request headers");

        String apiKey = request.getHeader(HEADER_NAME);
        
        if (StringUtils.hasText(apiKey)) {
            // 构造未认证的Token,交给AuthenticationManager处理
            Authentication unauthenticatedToken = new ApiKeyAuthenticationToken(apiKey);
            // 触发认证流程
            Authentication authenticatedToken = authenticationManager.authenticate(unauthenticatedToken);
            // 将认证结果存入SecurityContext
            SecurityContextHolder.getContext().setAuthentication(authenticatedToken);
        }

        // 必须继续执行过滤器链,不能return!
        filterChain.doFilter(request, response);
    }
}

3. 修改SecurityFilterChain配置,整合多认证逻辑

在你的filterChain Bean中,需要把自定义过滤器加到OAuth2认证过滤器之前,同时Spring Security会自动发现并使用你注册为Bean的ApiKeyAuthenticationProvider:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, 
                                       ApiKeyAuthenticationFilter apiKeyAuthenticationFilter) throws Exception {

    // 1. 注册自定义认证提供商(其实Spring会自动扫描Bean,这里显式写也可以)
    http.authenticationProvider(new ApiKeyAuthenticationProvider());

    // 2. 原有CORS配置保持不变
    http.cors(corsCustomizer ->
            corsCustomizer.configurationSource(request -> {
                CorsConfiguration config = new CorsConfiguration();
                config.setAllowedOrigins(List.of("http://localhost:4200"));
                config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
                config.setAllowCredentials(true);
                config.setExposedHeaders(List.of("Authorization"));
                config.setAllowedHeaders(List.of(
                        "Access-Control-Allow-Headers",
                        "Access-Control-Allow-Origin",
                        "Access-Control-Request-Method",
                        "Access-Control-Request-Headers",
                        "Origin",
                        "Cache-Control",
                        "Content-Type",
                        "Authorization"
                ));
                config.setMaxAge(3600L);
                return config;
            }));

    // 3. 原有Session配置保持不变
    http.sessionManagement(sessionManagementConfigurer ->
            sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

    // 4. 添加自定义API Key过滤器,放在OAuth2认证过滤器之前
    http.addFilterBefore(apiKeyAuthenticationFilter, OAuth2AuthenticationProcessingFilter.class);

    // 5. 原有AAD OAuth2配置保持不变
    http.oauth2ResourceServer(oauth2 ->
            oauth2.jwt(jwtConfigurer -> {
                jwtConfigurer.jwkSetUri(this.keySetUri);
                jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter());
            }));

    // 6. 原有授权规则保持不变
    http.authorizeHttpRequests(auth -> {
        auth.requestMatchers(HttpMethod.OPTIONS).permitAll();
        auth.requestMatchers("/actuator/**", "/api-docs").permitAll();
        auth.anyRequest().authenticated();
    });

    return http.build();
}

方案二:针对特定接口路由配置不同认证方式

如果你希望某些接口只用API Key认证,另一些只用AAD JWT认证,可以通过requestMatchers结合securityMatcher来拆分过滤器链:

// 第一个过滤器链:处理API Key专属接口
@Bean
@Order(1)
public SecurityFilterChain apiKeyFilterChain(HttpSecurity http) throws Exception {
    http.securityMatcher("/api/**/public/**")
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .addFilterBefore(apiKeyAuthenticationFilter, OAuth2AuthenticationProcessingFilter.class)
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .csrf(csrf -> csrf.disable())
        .oauth2ResourceServer(oauth2 -> oauth2.disable()); // 禁用AAD认证

    return http.build();
}

// 第二个过滤器链:处理AAD JWT专属接口
@Bean
@Order(2)
public SecurityFilterChain aadJwtFilterChain(HttpSecurity http) throws Exception {
    // 这里就是你原来的完整配置,处理非API Key专属的接口
    // ... 原有代码 ...
}

总结一下核心要点

  1. 不要中断过滤器链:设置完认证后一定要调用filterChain.doFilter(request, response),否则控制器无法处理请求返回内容
  2. 遵循Spring Security标准流程:用AuthenticationProvider处理认证逻辑,而不是在过滤器里直接设置已认证状态,这样更易维护
  3. 过滤器顺序很重要:自定义认证过滤器要放在官方的OAuth2认证过滤器之前,这样才能优先处理自定义认证请求

备注:内容来源于stack exchange,提问作者DasShorty

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 19:23:03