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

Spring Boot集成Firebase:JWT无效导致认证失败求助

Spring Boot: 3.0.4

配置情况

为验证Firebase Auth的JWT令牌,已完成以下配置:

1. 路由安全配置

application.yaml

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com"
          issuer-uri: "https://securetoken.google.com/<project-id>"

SecurityFilterChain 配置代码

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

    http.authorizeHttpRequests(
            (config) -> {
                try {
                    config
                            .requestMatchers(new AntPathRequestMatcher("/anon/**")).permitAll()
                            .anyRequest().authenticated()
                            .and().oauth2ResourceServer().jwt();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
    );

    return http.build();
}

2. FirebaseApp 配置

@Configuration
public class FirebaseConfig {

    private final static Logger LOGGER = LogManager.getLogger(FirebaseConfig.class);

    private final Environment environment;

    @Autowired
    public FirebaseConfig(Environment environment) {
        this.environment = environment;
    }

    @Bean
    public FirebaseApp firebaseApp() throws IOException {

        LOGGER.info("Initializing Firebase.");
        String googleCredentials = environment.getProperty("GOOGLE_FIREBASE_CREDENTIALS");
        if (googleCredentials == null) {
            throw new RuntimeException("GOOGLE_FIREBASE_CREDENTIALS not set.");
        }

        FileInputStream serviceAccount = new FileInputStream(googleCredentials);
        FirebaseOptions options = FirebaseOptions.builder()
                .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                .build();

        if (FirebaseApp.getApps().isEmpty()) {
            return FirebaseApp.initializeApp(options);
        }

        return FirebaseApp.getApps().get(0);
    }

    @Bean
    @DependsOn("firebaseApp")
    public FirebaseAuth firebaseAuth() {
        return FirebaseAuth.getInstance();
    }

}

问题现象

Angular应用向后端发送请求时,JwtAuthenticationProvider报错:Failed to authenticate since the JWT was invalid

日志内容:

2023-03-23T10:35:27.221+01:00 DEBUG 102007 --- [nio-8081-exec-1] o.s.security.web.FilterChainProxy        : Securing GET /hello-world
2023-03-23T10:35:27.221+01:00 DEBUG 102007 --- [nio-8081-exec-1] o.s.s.o.s.r.a.JwtAuthenticationProvider  : Failed to authenticate since the JWT was invalid

Angular端已通过AngularFire正确设置Authorization头:
Angular 代码

private auth: Auth = inject(Auth);

public user$ = user(this.auth);

public idToken$ = idToken(this.auth);

constructor(
  private logger: NGXLogger,
) {
  this.subs.add(this.user$.subscribe(user => this.user = user));
  this.subs.add(this.idToken$.subscribe(idToken => this.idToken = idToken}));
}

请求头详情

GET /hello-world HTTP/1.1
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en,en-AT;q=0.9,de;q=0.8
Authorization: Bearer ey..<the-token>
Cache-Control: no-cache
Connection: keep-alive
Host: localhost:8081
Origin: https://localhost:4200
Pragma: no-cache
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site

JWT 详情

// Header
{
  "alg": "none",
  "typ": "JWT"
}

// Body
{
  "name": "Raccoon Otter",
  "email": "raccoon.otter.271@example.com",
  "email_verified": true,
  "auth_time": 1679558072,
  "user_id": "ieZuX2TRrJHOufqFjhpYTtXMcAsB",
  "firebase": {
    "identities": {
      "email": [
        "raccoon.otter.271@example.com"
      ],
      "google.com": [
        "7776418049744228599441294898732774086098"
      ]
    },
    "sign_in_provider": "google.com"
  },
  "iat": 1679565375,
  "exp": 1679568975,
  "aud": "<project-id>",
  "iss": "https://securetoken.google.com/<project-id>",
  "sub": "ieZuX2TRrJHOufqFjhpYTtXMcAsB"
}

// Signature
<empty>

问题原因及解决方案

原因

Spring Security 默认要求JWT必须具备有效签名,而当前使用的JWT头中alg为none,且签名为空,属于无签名令牌,会被Spring Security直接判定无效。另外,Firebase正式环境的ID Token默认使用RS256算法签名,当前的无签名令牌大概率来自测试/模拟器环境。

解决方案

1. 确保获取有效签名的Firebase ID Token

在Angular端调用getIdToken()方法时,确保是从Firebase正式环境获取签名令牌,而非模拟器或测试环境生成的无签名令牌。可通过本地解码或Firebase SDK验证令牌的签名有效性。

2. 临时关闭签名验证(仅测试环境可用)

若仅用于测试,可自定义JWT解码器关闭签名验证,但生产环境绝对禁止此操作:

@Bean
public JwtDecoder jwtDecoder() {
    NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri("https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com").build();
    decoder.setJwtValidator(JwtValidators.createDefaultWithIssuer("https://securetoken.google.com/<project-id>")
            .and()
            .withClaimPresence("sub")
            .and()
            .withoutSignature()); // 关闭签名验证
    return decoder;
}

3. 使用Firebase SDK验证令牌(推荐生产环境)

利用已配置的FirebaseAuth Bean,通过官方SDK验证令牌,替代Spring Security默认JWT验证:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, FirebaseAuth firebaseAuth) throws Exception {
    http.authorizeHttpRequests(config -> config
            .requestMatchers(new AntPathRequestMatcher("/anon/**")).permitAll()
            .anyRequest().authenticated())
        .oauth2ResourceServer(oauth2 -> oauth2.authenticationConverter(new FirebaseJwtAuthenticationConverter(firebaseAuth)));
    return http.build();
}

// 自定义认证转换器
static class FirebaseJwtAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
    private final FirebaseAuth firebaseAuth;

    public FirebaseJwtAuthenticationConverter(FirebaseAuth firebaseAuth) {
        this.firebaseAuth = firebaseAuth;
    }

    @Override
    public Authentication convert(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return null;
        }
        String token = authHeader.substring(7);
        try {
            FirebaseToken decodedToken = firebaseAuth.verifyIdToken(token);
            Collection<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
            return new UsernamePasswordAuthenticationToken(decodedToken.getUid(), null, authorities);
        } catch (FirebaseAuthException e) {
            throw new AuthenticationServiceException("Invalid Firebase ID token", e);
        }
    }
}

此方式适配Firebase Auth令牌格式,能正确处理签名验证,安全性更高。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 09:48:08