【问题标题】:Using more than one JWT Decoder with Spring Webflux Security在 Spring Webflux Security 中使用多个 JWT 解码器
【发布时间】: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 是真的。阅读您的评论后,我专注于使其与两个手动实例化的解码器一起工作。如果您对我是如何做到的感到好奇,您可以查看我的答案:)

标签: spring-boot spring-security spring-webflux


【解决方案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

【讨论】:

    猜你喜欢
    • 2021-04-25
    • 2014-08-15
    • 2020-04-02
    • 2018-11-03
    • 2020-02-12
    • 2019-12-29
    • 2021-05-06
    • 1970-01-01
    • 2019-04-24
    相关资源
    最近更新 更多