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

Spring Resource Server集成Spring Security OAuth2(Google)认证失败:JWT无效问题求助

解决Google Access Token在Spring Security OAuth2资源服务器中验证失败的问题

首先,你踩的坑我之前也遇到过——Google返回的access_token并不是标准JWT格式,但你当前的Spring Security配置是按照JWT资源服务器来搭建的,JwtAuthenticationProvider自然会把它当成JWT去解析,结果肯定报错。

下面给你两种落地可行的解决方案,按需选择:

方案一:适配现有Access Token(无需前端改动)

这种方案不用麻烦前端调整,后端通过调用Google官方的token验证接口来校验合法性:

  1. 确保依赖齐全:项目中需要引入Spring Security OAuth2资源服务器和WebClient依赖(用来调用Google的验证接口)
  2. 自定义Token验证逻辑:
    • 写一个自定义的AuthenticationProvider,调用Google的tokeninfo接口校验access_token
    • 替换Spring Security默认的JWT验证逻辑,改用这个自定义Provider

示例Kotlin代码:

@Component
class GoogleAccessTokenAuthProvider(private val webClient: WebClient) : AuthenticationProvider {
    override fun authenticate(authentication: Authentication): Authentication {
        val bearerToken = authentication.credentials.toString()
        // 调用Google的token验证接口
        val tokenInfo = webClient.get()
            .uri("https://www.googleapis.com/oauth2/v3/tokeninfo?access_token={token}", bearerToken)
            .retrieve()
            .bodyToMono(GoogleTokenInfo::class.java)
            .block() ?: throw BadCredentialsException("无效的Token")
        
        // 可选但推荐:验证Token的受众是否匹配你的客户端ID
        if (tokenInfo.aud != "<你的Google客户端ID>") {
            throw BadCredentialsException("Token受众不匹配")
        }
        
        // 构建认证成功的身份信息
        val authorities = listOf(SimpleGrantedAuthority("ROLE_USER"))
        return UsernamePasswordAuthenticationToken(tokenInfo.email, null, authorities)
    }

    override fun supports(authentication: Class<*>): Boolean {
        return BearerTokenAuthenticationToken::class.java.isAssignableFrom(authentication)
    }
}

// 接收Google返回的Token信息的数据类
data class GoogleTokenInfo(
    val aud: String,
    val email: String,
    val exp: Long,
    val sub: String
)

然后修改Spring Security配置,注册这个自定义Provider:

@Configuration
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SpringSecurityConfig(
    private val googleAccessTokenAuthProvider: GoogleAccessTokenAuthProvider
) {
    @Bean
    fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http
            .authorizeRequests()
            .antMatchers("/**").fullyAuthenticated()
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .oauth2ResourceServer()
                .bearerTokenResolver(BearerTokenResolver { it.getHeader("Authorization")?.removePrefix("Bearer ") })
                .authenticationManager(ProviderManager(listOf(googleAccessTokenAuthProvider)))
            .and()
            .cors().and().csrf().disable()
        return http.build()
    }

    @Bean
    fun webClient(): WebClient {
        return WebClient.create()
    }
}

方案二:改用Google的ID Token(更简洁)

如果能协调前端修改,让前端在Google登录时获取id_token(这是标准JWT格式),那你当前的大部分配置都能复用:

  1. 前端请求Google授权时,确保scope包含openid(你已经配置了),并且获取id_token而非access_token
  2. 后端的Spring Security配置不用大改——你当前的issuer-uri和jwk-set-uri配置是正确的,直接用就行

当前端把id_token放在Authorization头里发送请求时,Spring Security的JWT验证逻辑就能正常工作了。

问题根源补充

Google OAuth2返回的access_token是不透明令牌,只用来访问Google的API,本身不是JWT;而id_token是符合OpenID Connect标准的JWT,包含用户身份信息,且可以通过公钥验证签名,这才是Spring Security OAuth2资源服务器默认支持的令牌类型。


内容的提问来源于stack exchange,提问作者Yan Frankovski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 18:08:10