在使用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专属的接口 // ... 原有代码 ... }
总结一下核心要点
- 不要中断过滤器链:设置完认证后一定要调用
filterChain.doFilter(request, response),否则控制器无法处理请求返回内容 - 遵循Spring Security标准流程:用
AuthenticationProvider处理认证逻辑,而不是在过滤器里直接设置已认证状态,这样更易维护 - 过滤器顺序很重要:自定义认证过滤器要放在官方的OAuth2认证过滤器之前,这样才能优先处理自定义认证请求
备注:内容来源于stack exchange,提问作者DasShorty

