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

如何用Reactive Spring Security验证JWT特定Claim并组合权限检查

问题描述

我有一个应用,用户需要拥有access权限才能使用。当前的Spring Security响应式配置如下:

@Bean
fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain = http {
    csrf { disable() }
    formLogin { disable() }
    logout { disable() }
    httpBasic { disable() }
    authorizeExchange {
        authorize(anyExchange, hasAuthority("access"))
    }
    oauth2ResourceServer { jwt {} }
}

现在需要添加额外的JWT Claim校验:验证JWT中resource_access Claim是否包含指定密钥(比如my-resource),期望能写出类似这样的授权逻辑:

authorize(anyExchange, hasAuthority("access") && hasResourceAccess("my-resource"))

要求不重写现有逻辑,采用响应式实现。之前尝试过两种方案但未成功:

  • OAuth2TokenValidator方案:自定义了验证器,但无法设置为JwtValidator,且没有可用的issuer-uri来初始化ReactiveJwtDecoder。
  • ReactiveAuthorizationManager<AuthorizationContext>方案:自定义管理器后,不知道如何与AuthorityReactiveAuthorizationManager组合使用,DelegatingReactiveAuthorizationManager类型不匹配,不想自己重写整合逻辑。

方案一:自定义JWT转换器添加额外权限(推荐)

不需要自定义验证器,而是通过JwtAuthenticationConverter将resource_access中的密钥转换为额外权限,直接用hasAuthority组合判断,完全重用现有逻辑:

@Bean
fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
    val grantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter()
    // 保留原有的权限转换逻辑(比如从scope/roles中提取权限)
    grantedAuthoritiesConverter.setAuthorityPrefix("") // 根据实际需求调整前缀

    val customConverter = JwtAuthenticationConverter { jwt ->
        val auth = grantedAuthoritiesConverter.convert(jwt) ?: emptyList()
        // 提取resource_access中的my-resource,存在则添加对应权限
        val resourceAccess = jwt.getClaimAsMap("resource_access") as? Map<String, *>
        val hasMyResource = resourceAccess?.containsKey("my-resource") == true
        if (hasMyResource) {
            auth + SimpleGrantedAuthority("RESOURCE_MY_RESOURCE")
        } else {
            auth
        }
    }
    return customConverter
}

// 修改filterChain配置,应用自定义转换器
@Bean
fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain = http {
    csrf { disable() }
    formLogin { disable() }
    logout { disable() }
    httpBasic { disable() }
    authorizeExchange {
        authorize(anyExchange, hasAuthority("access") and hasAuthority("RESOURCE_MY_RESOURCE"))
    }
    oauth2ResourceServer { 
        jwt {
            jwtAuthenticationConverter = jwtAuthenticationConverter()
        }
    }
}

这种方式的好处是完全复用Spring Security的权限校验逻辑,不需要自定义授权管理器,代码简洁且符合响应式模型。


方案二:组合ReactiveAuthorizationManager

如果不想通过权限转换,直接在授权逻辑中校验Claim,可以自定义ReactiveAuthorizationManager<AuthorizationContext>,再用AndReactiveAuthorizationManager与原有的AuthorityReactiveAuthorizationManager组合:

// 自定义校验resource_access的授权管理器
class ResourceAccessAuthorizationManager(private val resourceName: String) : ReactiveAuthorizationManager<AuthorizationContext> {
    override fun check(authentication: Mono<Authentication>, context: AuthorizationContext): Mono<AuthorizationDecision> {
        return authentication
            .filter { it is JwtAuthenticationToken }
            .cast(JwtAuthenticationToken::class.java)
            .map { it.token }
            .map { jwt ->
                val resourceAccess = jwt.getClaimAsMap("resource_access") as? Map<String, *>
                val hasAccess = resourceAccess?.containsKey(resourceName) == true
                AuthorizationDecision(hasAccess)
            }
            .defaultIfEmpty(AuthorizationDecision(false))
    }
}

// 工具方法,方便配置中直接调用
fun hasResourceAccess(resourceName: String): ReactiveAuthorizationManager<AuthorizationContext> {
    return ResourceAccessAuthorizationManager(resourceName)
}

// 修改filterChain配置,组合两个授权条件
@Bean
fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain = http {
    csrf { disable() }
    formLogin { disable() }
    logout { disable() }
    httpBasic { disable() }
    authorizeExchange {
        val hasAccess = hasAuthority("access")
        val hasMyResource = hasResourceAccess("my-resource")
        authorize(anyExchange, AndReactiveAuthorizationManager(hasAccess, hasMyResource))
    }
    oauth2ResourceServer { jwt {} }
}

AndReactiveAuthorizationManager会同时校验两个授权条件,只有都通过才允许访问,完全重用了原有的hasAuthority逻辑,无需手动整合。


无issuer-uri的ReactiveJwtDecoder配置

如果没有issuer-uri,可以直接使用NimbusReactiveJwtDecoder手动配置JWK源:

方式1:指定JWK Set URL

@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    val jwkSetUri = "https://your-auth-server/.well-known/jwks.json"
    val jwkSource = JwkSetUriReactiveJwkSource(jwkSetUri)
    val jwtDecoder = NimbusReactiveJwtDecoder(jwkSource)
    // 可添加自定义JwtValidator
    jwtDecoder.setJwtValidator(DelegatingJwtValidator(
        JwtValidators.createDefaultWithIssuer("your-issuer"), // 如有issuer则添加
        MyAccessValidator()
    ))
    return jwtDecoder
}

方式2:使用静态公钥

@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    val publicKey = // 从文件或配置中加载你的公钥
    return NimbusReactiveJwtDecoder.withPublicKey(publicKey).build()
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 04:52:37