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
相关产品推荐
相关产品推荐

