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

配置Spring资源服务器自动获取Auth0用户完整信息

问题:资源服务器如何自动获取Auth0 userinfo并存入SecurityContextHolder

我已配置针对Auth0认证服务器验证JWT令牌的资源服务器,通过issuer-uri指定了Auth0地址。用户在公共客户端通过Auth0认证后获取JWT令牌,用该令牌请求资源服务器可成功授权,但SecurityContextHolder仅包含JWT解析出的基础数据,无用户姓名、邮箱等完整信息。Auth0提供的userinfo端点可获取这些信息,我希望将用户邮箱、姓名存入SecurityContextHolder,想知道是否可通过配置自动实现,或最佳实现方式是什么?

现有配置代码

安全过滤器链配置

@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http.authorizeRequests().anyRequest().permitAll()
        .and()
        .oauth2ResourceServer().jwt();
    return http.build()
}

JWT解码器Bean

@Bean
fun jwtDecoder(): JwtDecoder? {
    val jwtDecoder = JwtDecoders.fromOidcIssuerLocation<JwtDecoder>(issuer) as NimbusJwtDecoder
    val audienceValidator: OAuth2TokenValidator<Jwt> = AudienceValidator(audience)
    val withIssuer = JwtValidators.createDefaultWithIssuer(issuer)
    val withAudience: OAuth2TokenValidator<Jwt> = DelegatingOAuth2TokenValidator(withIssuer, audienceValidator)
    jwtDecoder.setJwtValidator(withAudience)
    return jwtDecoder
}

application.properties

spring.security.oauth2.resourceserver.jwt.issuer-uri=my-domain.com
spring.security.oauth2.resourceserver.jwt.audience=my-audience

从Auth0获取的JWT负载

{
  "iss": "https://dev-abcdefgh.us.auth0.com/",
  "sub": "google-oauth2|353335637216442227159",
  "aud": [
    "my-audience",
    "https://dev-3ag8q43b.us.auth0.com/userinfo"
  ],
  "iat": 1663100248,
  "exp": 1663186648,
  "azp": "m01yBdKdQd5erBxriQde24ogfsdAsYvD",
  "scope": "openid profile email"
}

解决方案

方案一:自定义JWT转换器,调用userinfo端点补充用户信息

通过自定义JwtAuthenticationConverter,在JWT转换为Authentication对象的过程中,调用Auth0的userinfo端点获取用户完整信息,并将其存入Authentication的详情中。

1. 配置WebClient用于调用userinfo端点

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

2. 实现自定义JwtAuthenticationConverter

@Component
class CustomJwtAuthenticationConverter(
    private val webClient: WebClient,
    @Value("\${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private val issuerUri: String
) : JwtAuthenticationConverter() {

    override fun convert(jwt: Jwt): AbstractAuthenticationToken {
        val baseAuth = super.convert(jwt) as JwtAuthenticationToken
        
        // 调用Auth0 userinfo端点获取用户信息
        val userInfo = webClient.get()
            .uri("${issuerUri}userinfo")
            .headers { it.setBearerAuth(jwt.tokenValue) }
            .retrieve()
            .bodyToMono<Map<String, Any>>()
            .block() // 同步调用,若需异步可调整为非阻塞方式

        // 将用户信息合并到Authentication详情中
        val mergedDetails = mutableMapOf<String, Any>().apply {
            putAll(baseAuth.details as Map<String, Any>)
            userInfo?.let { putAll(it) }
        }

        return JwtAuthenticationToken(
            jwt,
            baseAuth.authorities,
            baseAuth.name
        ).apply {
            details = mergedDetails
        }
    }
}

3. 配置过滤器链使用自定义转换器

@Bean
fun filterChain(http: HttpSecurity, customConverter: CustomJwtAuthenticationConverter): SecurityFilterChain {
    http.authorizeRequests().anyRequest().permitAll()
        .and()
        .oauth2ResourceServer()
        .jwt()
        .jwtAuthenticationConverter(customConverter); // 绑定自定义转换器
    return http.build()
}

方案二:配置Auth0将用户信息嵌入JWT(推荐)

每次请求调用userinfo会增加网络开销和响应延迟,更高效的方式是在Auth0控制台配置,让JWT负载直接包含用户邮箱、姓名等信息。

操作步骤:

  1. 登录Auth0控制台,进入目标应用的Actions > Flows > Login
  2. 添加一个自定义Action,编写代码将用户属性注入JWT:
exports.onExecutePostLogin = async (event, api) => {
  const namespace = 'https://your-app-namespace/'; // 自定义命名空间,避免字段冲突
  api.idToken.setCustomClaim(`${namespace}email`, event.user.email);
  api.idToken.setCustomClaim(`${namespace}name`, event.user.name);
  api.idToken.setCustomClaim(`${namespace}picture`, event.user.picture);
};
  1. 保存并启用该Action,用户后续获取的JWT将包含这些自定义字段。

解析JWT中的自定义字段

此时只需修改JwtAuthenticationConverter,解析JWT中的自定义字段并存入Authentication:

@Component
class CustomJwtAuthenticationConverter : JwtAuthenticationConverter() {
    private val namespace = "https://your-app-namespace/"

    override fun extractAuthorities(jwt: Jwt): Collection<GrantedAuthority> {
        // 保留原有权限逻辑,或自定义权限解析
        return super.extractAuthorities(jwt)
    }

    override fun convert(jwt: Jwt): AbstractAuthenticationToken {
        val auth = super.convert(jwt) as JwtAuthenticationToken
        val userDetails = mapOf(
            "email" to jwt.claims["${namespace}email"],
            "name" to jwt.claims["${namespace}name"]
        )
        auth.details = userDetails
        return auth
    }
}

方案对比

  • 方案一:无需修改Auth0配置,快速实现,但每次请求都发起额外网络调用,性能开销较大,适合临时场景。
  • 方案二:无额外网络请求,性能更优,是生产环境的推荐方案,但需要在Auth0控制台进行配置。

内容的提问来源于stack exchange,提问作者Denis Stephanov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 06:45:37