在 Spring Webflux Security 中使用多个 JWT 解码器

Posted

技术标签:

【中文标题】在 Spring Webflux Security 中使用多个 JWT 解码器【英文标题】:Using more than one JWT Decoder with Spring Webflux Security 【发布时间】:2021-10-11 18:19:30 【问题描述】:

我阅读了 this post 关于在 Spring Security 流程中使用多个 JWT 解码器的文章,这似乎很容易,除了我使用的是 Spring Webflux 而不是 Spring WebMVC ,它有方便的WebSecurityConfigurerAdapter,您可以扩展它以添加多个AuthenticationProvider 实例。使用 Webflux,您不再需要扩展某些类来配置安全性。

那么在尝试使用 Webflux 复制此内容时有什么问题? This 。正如您所读到的那样,Webflux 不使用 AuthenticationProvider ,您必须声明一个 ReactiveAuthenticationManager 。问题是我不知道如何让 Spring 使用多个身份验证管理器,每个身份验证管理器都使用自己的ReactiveJwtDecoder

我的第一个身份验证管理器将是 spring 使用此属性自动创建的一个:

      security:
        oauth2:
          resourceserver:
            jwt:
              issuer-uri: $scacap.auth0.issuer

我的第二个身份验证管理器将是我在安全@Configuration 中声明的自定义管理器:

    @Configuration
    @EnableWebFluxSecurity
    @EnableReactiveMethodSecurity
    @EnableConfigurationProperties(JwkProperties::class)
    internal class SecurityConfiguration 
    
        @Bean
        fun securityFilter(
            http: ServerHttpSecurity,
            scalableAuthenticationManager: JwtReactiveAuthenticationManager
        ): SecurityWebFilterChain 
    
            http.csrf().disable()
                .authorizeExchange()
                .anyExchange().authenticated()
                .and()
                .oauth2ResourceServer().jwt()
                .jwtAuthenticationConverter(Auth0AuthenticationConverter())
    
            return http.build()
        
    
        @Bean
        fun customAuthenticationManager(jwkProperties: JwkProperties): JwtReactiveAuthenticationManager 
            val decoder = NimbusReactiveJwtDecoder.withJwkSource  Flux.fromIterable(jwkProperties.jwkSet.keys) .build()
            return JwtReactiveAuthenticationManager(decoder).also 
                it.setJwtAuthenticationConverter(ScalableAuthenticationConverter())
            
        
    

我正在调试,似乎只选择了一个身份验证管理器,因此只能验证 auth0 令牌,但我也想用我自己的 JWKS 验证令牌

【问题讨论】:

要么在属性中指定所有配置,要么在自定义 HttpSecurity 配置中覆盖。您不能在属性中指定内容(使用 springs 自动配置),然后创建一个完整的自定义配置并期望两者都能正常工作。 @Toerktumlare 是真的。阅读您的评论后,我专注于使其与两个手动实例化的解码器一起工作。如果您对我是如何做到的感到好奇,您可以查看我的答案:) 【参考方案1】:

好的,这就是我最终要做的:

我没有尝试将多个AuthenticationManagers 传递给Spring Security 流,而是创建了一个包装器,我称之为DualAuthenticationManager。这样对于 Spring,只有一个经理,我在包装器中进行编排,例如 firstManager.authenticate(auth).onErrorResume secondManager.authenticate(auth)

它最终比我想象的要短。这一切都在我的安全@Configuration 中的@Bean 函数中。每个经理都有自己的转换器功能,所以我可以使用两个不同的 JWT 创建我的 UserToken 模型:)

    @Configuration
    @EnableWebFluxSecurity
    @EnableReactiveMethodSecurity
    @EnableConfigurationProperties(*[JwtProperties::class, Auth0Properties::class])
    internal class SecurityConfiguration(
        private val jwtProperties: JwtProperties,
        private val auth0Properties: Auth0Properties
    ) 
    
        @Bean
        fun securityFilter(
            http: ServerHttpSecurity,
            dualAuthManager: ReactiveAuthenticationManager
        ): SecurityWebFilterChain 
            http.csrf().disable()
                .authorizeExchange()
                .pathMatchers("/actuator/**").permitAll()
                .pathMatchers("/user/**").hasAuthority(Authorities.USER)
                .anyExchange().authenticated()
                .and()
                .oauth2ResourceServer().jwt()
                .authenticationManager(dualAuthManager)
    
            return http.build()
        
    
        @Bean
        fun dualAuthManager(): ReactiveAuthenticationManager 
            val firstManager = fromOidcIssuerLocation(auth0Properties.issuer).let  decoder ->
                JwtReactiveAuthenticationManager(decoder).also 
                    it.setJwtAuthenticationConverter(FirstAuthenticationConverter())
                
            
    
            val secondManager = withJwkSource  fromIterable(jwtProperties.jwkSet.keys) .build().let  decoder ->
                JwtReactiveAuthenticationManager(decoder).also 
                    it.setJwtAuthenticationConverter(SecondAuthenticationConverter())
                
            
    
            return ReactiveAuthenticationManager  auth ->
                firstManager.authenticate(auth).onErrorResume  secondManager.authenticate(auth) 
            
        
    

这是我的转换器的外观:

    class FirstAuthenticationConverter : Converter<Jwt, Mono<AbstractAuthenticationToken>> 
    
        override fun convert(jwt: Jwt): Mono<AbstractAuthenticationToken> 
            val authorities = jwt.getClaimAsStringList(AUTHORITIES) ?: emptyList()
            val userId = jwt.getClaimAsString(PERSON_ID)
            val email = jwt.getClaimAsString(EMAIL)
    
            return Mono.just(
                UsernamePasswordAuthenticationToken(
                    UserToken(jwt.tokenValue, UserTokenType.FIRST, userId, email),
                    null,
                    authorities.map  SimpleGrantedAuthority(it) 
                )
            )
        
    

然后在我的控制器中,我得到了我在转换器中构建的对象:

@AuthenticationPrincipal userToken: UserToken

【讨论】:

以上是关于在 Spring Webflux Security 中使用多个 JWT 解码器的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring WebFlux 中使用 Spring Security 实现身份验证的资源是啥

如何在 Spring WebFlux Security(Reactive Spring Security)配置中将多个用户角色添加到单个 pathMatcher/Route?

在身份验证 Spring Security + WebFlux 期间抛出和处理自定义异常

Spring WebFlux + Security - 我们有“记住我”功能吗?

WebFlux Spring Security配置

如何在 Spring webflux 应用程序中使用 Spring WebSessionIdResolver 和 Spring Security 5?