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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 17:34:58