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



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

那么用Webflux复制这个过程有什么问题呢?这一点。正如你所看到的,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验证令牌

好的,这就是我最后做的:

我没有尝试通过某种方式将几个AuthenticationManager传递到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

相关内容

最新更新