如何用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
相关产品推荐
相关产品推荐

