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

Spring Boot Security结合Firebase认证:白名单URL失效问题排查

问题排查:Spring Boot API白名单路径仍触发认证过滤器

问题描述

用Java Spring Boot搭建带基础CRUD的API,希望/api/v1/habit/**路径加入白名单无需认证,/user等其他路径需认证保护。但当前所有请求(包括白名单路径)都会触发Firebase认证过滤器,提示必须携带认证Token。

相关代码

AuthenticationFilter

public class FirebaseAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private static final String AUTH_HEADER = "Authorization";

    public FirebaseAuthenticationFilter() {
        super(request -> true);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        final String token = request.getHeader(AUTH_HEADER);
        if(token == null) {
            throw new IllegalArgumentException("No Auth Token in header - " + AUTH_HEADER);
        }
        try {
            FirebaseToken firebaseToken = FirebaseAuth.getInstance().verifyIdToken(token);
            AbstractAuthenticationToken authenticationToken = new FirebaseAuthenticationToken(firebaseToken);
            return getAuthenticationManager().authenticate(authenticationToken);
        }
        catch (FirebaseException e) {
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        // This updates the SecurityContextHolder
        super.successfulAuthentication(request, response, chain, authResult);
        chain.doFilter(request,response);
    }
}

Authentication Provider

@Component
public class FirebaseAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        FirebaseAuthenticationToken firebaseAuthenticationToken = (FirebaseAuthenticationToken) authentication;
        firebaseAuthenticationToken.setAuthenticated(true);
        return firebaseAuthenticationToken;
    }

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

AuthenticationToken(Model)

public class FirebaseAuthenticationToken extends AbstractAuthenticationToken {

    private final FirebaseToken firebaseToken;

    public FirebaseAuthenticationToken(FirebaseToken firebaseToken)  {
        super(null);
        this.firebaseToken = firebaseToken;
        setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return firebaseToken;
    }

    public String getEmail() {
        return firebaseToken.getEmail();
    }

    public String getAuthUid() {
        return firebaseToken.getUid();
    }

    @Override
    public Object getPrincipal() {
        return firebaseToken.getClaims();
    }

    @Override
    public boolean implies(Subject subject) {
        return super.implies(subject);
    }
}

SecurityConfig

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    final private FirebaseAuthenticationProvider firebaseAuthenticationProvider;
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        FirebaseAuthenticationFilter filter = new FirebaseAuthenticationFilter();
        //AuthenticationManager is responsible for invoking the authentication provider(s) to authenticate the user.
        filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));

        http
                .csrf().disable()
                .authenticationProvider(firebaseAuthenticationProvider)
                .authorizeHttpRequests((authz) -> authz
                        .requestMatchers("/api/v1/habit/**").permitAll()
                        .anyRequest().authenticated()
                )
                .addFilterAt(filter, BasicAuthenticationFilter.class);
        return http.build();
    }
}

问题原因

核心问题出在FirebaseAuthenticationFilter的构造函数:

public FirebaseAuthenticationFilter() {
    super(request -> true);
}

这里传入的request -> true表示所有请求都会触发这个过滤器,完全无视SecurityConfig里配置的白名单规则。因为Spring Security的过滤器链中,认证过滤器的执行优先级高于授权规则判断,所以即使是permitAll的路径,也会先进入这个过滤器的attemptAuthentication方法,而该方法强制要求请求携带Authorization头,导致白名单路径也被拦截。

解决方案

方案1:修改过滤器的请求匹配规则(推荐)

修改FirebaseAuthenticationFilter的构造函数,只对需要认证的路径生效,或者排除白名单路径:

方式A:指定需要认证的路径

比如只拦截/api/v1/user/**等需要认证的路径:

public FirebaseAuthenticationFilter() {
    super(new AntPathRequestMatcher("/api/v1/user/**"));
}

如果有多个需要认证的路径,可以用OrRequestMatcher组合:

public FirebaseAuthenticationFilter() {
    super(new OrRequestMatcher(
        new AntPathRequestMatcher("/api/v1/user/**"),
        new AntPathRequestMatcher("/api/v1/other-protected/**")
    ));
}

方式B:排除白名单路径

如果需要保护的路径太多,更高效的方式是排除白名单路径:

public FirebaseAuthenticationFilter() {
    super(request -> !new AntPathRequestMatcher("/api/v1/habit/**").matches(request));
}

方案2:在过滤器内判断路径并放行

在attemptAuthentication方法开头,先检查请求路径是否属于白名单,如果是则直接放行(不过这种方式不如方案1规范,因为过滤器应该只处理需要认证的请求):

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws AuthenticationException, IOException, ServletException {
    // 检查是否是白名单路径
    if(new AntPathRequestMatcher("/api/v1/habit/**").matches(request)){
        chain.doFilter(request, response);
        return null;
    }
    // 原有认证逻辑
    final String token = request.getHeader(AUTH_HEADER);
    if(token == null) {
        throw new IllegalArgumentException("No Auth Token in header - " + AUTH_HEADER);
    }
    // ... 剩余代码
}

额外优化点

  1. FirebaseAuthenticationToken构造函数里直接调用setAuthenticated(true)会绕过认证提供者的逻辑,建议去掉这行,让FirebaseAuthenticationProvider来处理认证状态。
  2. attemptAuthentication中抛出的IllegalArgumentException可以替换为Spring Security提供的BadCredentialsException或AuthenticationServiceException,更符合Spring Security的异常体系。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 06:32:23