Spring Boot配置带认证的Pub/Sub Push端点遇JWT验证问题
解决方案:整合Firebase与Pub/Sub的JWT验证逻辑
问题核心在于:Spring Security的oauth2ResourceServer过滤器会在授权判断(permitAll)之前执行,因此即使/pubsub端点被设为允许所有访问,过滤器仍会尝试用Firebase的JWK配置验证Pub/Sub的JWT,导致验证失败。以下是两种可行的解决方式:
方式一:配置多JWT解码器,按端点区分验证逻辑
1. 添加Pub/Sub的JWK配置
在application.properties中新增Pub/Sub的JWK URI:
# Firebase JWT配置 spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://www.googleapis.com/service_accounts/v1/jwk/securetoken%40system.gserviceaccount.com # Pub/Sub JWT配置 spring.security.oauth2.resourceserver.jwt.pubsub-jwk-set-uri=https://www.googleapis.com/service_accounts/v1/jwk/pubsub.googleapis.com
2. 创建两个JWT解码器Bean
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Qualifier; @Configuration public class JwtDecoderConfig { @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") private String firebaseJwkUri; @Value("${spring.security.oauth2.resourceserver.jwt.pubsub-jwk-set-uri}") private String pubsubJwkUri; @Bean("firebaseJwtDecoder") public JwtDecoder firebaseJwtDecoder() { return NimbusJwtDecoder.withJwkSetUri(firebaseJwkUri).build(); } @Bean("pubsubJwtDecoder") public JwtDecoder pubsubJwtDecoder() { return NimbusJwtDecoder.withJwkSetUri(pubsubJwkUri).build(); } }
3. 修改SecurityFilterChain配置
根据请求端点动态选择对应的JWT解码器:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Qualifier; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtDecoder firebaseJwtDecoder; private final JwtDecoder pubsubJwtDecoder; private final JwtAuthenticationConverter customizer; public SecurityConfig(@Qualifier("firebaseJwtDecoder") JwtDecoder firebaseJwtDecoder, @Qualifier("pubsubJwtDecoder") JwtDecoder pubsubJwtDecoder, JwtAuthenticationConverter customizer) { this.firebaseJwtDecoder = firebaseJwtDecoder; this.pubsubJwtDecoder = pubsubJwtDecoder; this.customizer = customizer; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .cors(cors -> cors.disable()) .csrf(csrf -> csrf.disable()) .anonymous(anon -> anon.authorities("ROLE_ANONYMOUS")) .authorizeHttpRequests(auth -> auth .requestMatchers("/pubsub").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .decoder(request -> request.getRequestURI().startsWith("/pubsub") ? pubsubJwtDecoder : firebaseJwtDecoder) .jwtAuthenticationConverter(customizer) ) ); return http.build(); } }
方式二:让/pubsub端点跳过Firebase JWT过滤器,手动验证Pub/Sub令牌
1. 修改SecurityFilterChain配置
让/pubsub端点跳过oauth2ResourceServer过滤器:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationConverter customizer; public SecurityConfig(JwtAuthenticationConverter customizer) { this.customizer = customizer; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .cors(cors -> cors.disable()) .csrf(csrf -> csrf.disable()) .anonymous(anon -> anon.authorities("ROLE_ANONYMOUS")) .authorizeHttpRequests(auth -> auth .requestMatchers("/pubsub").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(customizer)) ) // 仅对非/pubsub的请求应用oauth2ResourceServer过滤器 .requestMatcher(request -> !request.getRequestURI().startsWith("/pubsub")); return http.build(); } }
2. 在控制器中手动验证Pub/Sub JWT
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; import org.springframework.context.annotation.Qualifier; @RestController public class PubSubController { private final JwtDecoder pubsubJwtDecoder; public PubSubController(@Qualifier("pubsubJwtDecoder") JwtDecoder pubsubJwtDecoder) { this.pubsubJwtDecoder = pubsubJwtDecoder; } @PostMapping("/pubsub") public String handlePubSubMessage(@RequestHeader("Authorization") String authHeader) { // 提取Bearer令牌 String token = authHeader.replace("Bearer ", ""); // 验证JWT Jwt jwt = pubsubJwtDecoder.decode(token); // 额外验证签发者(可选,增强安全性) String issuer = jwt.getIssuer(); if (!"https://accounts.google.com".equals(issuer) && !"pubsub.googleapis.com".equals(issuer)) { throw new RuntimeException("无效的令牌签发者"); } // 处理Pub/Sub消息逻辑 return "消息处理完成"; } }
内容的提问来源于stack exchange,提问作者Arash
相关产品推荐
相关产品推荐

