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

Spring Boot双安全配置(JWT+API Key)异常排查与正确配置

解决Spring Boot多安全策略配置问题:JWT与API Key共存

问题根源分析

  • 第二个配置类MySecurityConfiguration中的configure(WebSecurity web)将/api/users/**加入了忽略列表,导致该路径的请求完全绕过Spring Security过滤器链,包括你定义的APIKeyAuthFilter,这就是API Key请求未走对应过滤器、且无JWT也能直接访问的核心原因。
  • 尽管第一个配置类APISecurityConfig设置了@Order(1)和antMatcher("/api/users/**"),但WebSecurity的忽略规则优先级高于HttpSecurity配置,请求会直接跳过所有安全验证逻辑。

解决方案

  1. 移除MySecurityConfiguration中对/api/users/**的忽略配置
  2. 确保两个配置类的antMatcher明确划分路径范围,避免逻辑冲突
  3. 修正APIKeyAuthFilter的逻辑:认证失败时直接返回401,防止请求流入后续过滤器链

修正后的代码

1. 主Security配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Configuration  
    @Order(1)
    public static class APISecurityConfig extends WebSecurityConfigurerAdapter {
        
        @Value("${my.api.key.header}") 
        private String principalRequestHeader;
        @Value("${my.api.key.token}") 
        private String principalRequestValue;
        
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity
                .cors().disable().csrf().disable()
                // 明确仅处理/api/users/**路径
                .antMatcher("/api/users/**")
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new APIKeyAuthFilter(principalRequestHeader, principalRequestValue), UsernamePasswordAuthenticationFilter.class);
        }
    }
    
    @Order(2)
    @Configuration
    public static class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
         
        @Autowired
        UserDetailsService userDetailsService;
            
        @Bean
        public AuthTokenFilter authenticationJwtTokenFilter() {
            return new AuthTokenFilter();
        }
        
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {      
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
        
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
        
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
            
        // 仅保留真正无需认证的公开接口
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/users/UserEmailExist", "/users/User/Add", "/users/Authenticate", "/users/User/ChangePassword");
        }
        
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {          
            httpSecurity
                .cors().disable().csrf().disable()
                // 明确处理/users/**路径
                .antMatcher("/users/**")
                .authorizeRequests()
                .antMatchers("/users/UserEmailExist", "/users/User/Add", "/users/Authenticate", "/users/User/ChangePassword")
                .permitAll()
                .antMatchers("/users/**").hasAnyRole("ADMIN_USER", "MANAGER_USER")
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        }
    }
}

2. 修正后的APIKeyAuthFilter

public class APIKeyAuthFilter extends GenericFilterBean  {
    
    private String principalRequestHeader;  
    private String principalRequestValue;
        
    public APIKeyAuthFilter(String principalRequestHeader, String principalRequestValue) {
        super();
        this.principalRequestHeader = principalRequestHeader;
        this.principalRequestValue = principalRequestValue;
    }
            
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
            
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        String apiKey = httpRequest.getHeader(principalRequestHeader);
        if (apiKey == null) {
            // 无API Key直接返回401
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpResponse.getWriter().write("API Key missing");
            return;
        }
        
        if (!apiKey.equals(principalRequestValue)) {
            // API Key无效返回401
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpResponse.getWriter().write("Invalid API Key");
            return;
        }
        
        // 认证通过,设置安全上下文
        ApiKeyAuthenticationToken apiToken = new ApiKeyAuthenticationToken(apiKey, AuthorityUtils.NO_AUTHORITIES);
        SecurityContextHolder.getContext().setAuthentication(apiToken);
        
        chain.doFilter(request, response);
    }
}

关键说明

  • WebSecurity忽略规则:仅对完全不需要认证的公开接口设置忽略,不要包含需要API Key或JWT认证的路径。
  • @Order优先级:@Order(1)的配置类会优先处理请求,当请求匹配/api/users/**时,会由APISecurityConfig单独处理,不会流入后续的JWT配置逻辑。
  • 过滤器逻辑优化:APIKeyAuthFilter在认证失败时直接终止请求,避免无效请求进入后续过滤器链,确保路径认证逻辑的单一性。

内容的提问来源于stack exchange,提问作者user2304483

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 05:24:32