@PreAuthorize 如何在响应式应用程序中工作或如何在没有 ThreadLocal 的情况下生活?

How @PreAuthorize is working in an Reactive Application or how to live without ThreadLocal?

您能否解释处理 @PreAuthorize("hasRole('ADMIN')") 的建议在 Reactive 应用程序中检索 SecurityContext 的位置?

下面的 Spring 安全示例很好地说明了这种用法:https://github.com/spring-projects/spring-security/tree/5.0.0.M4/samples/javaconfig/hellowebflux-method

在查看 Spring Security Webflux 源代码后,我发现了一些 SecurityContextRepository 的实现,但加载方法需要 ServerWebExchange 作为参数。

我试图了解如何替换标准服务中的 SecurityContextHolder.getContext().getAuthentication() 调用(因为 ThreadLocal 不再是响应式应用程序中的一个选项),但我不明白如何将其替换为对 SecurityContextRepository 的调用而不引用 ServerWebExchange.

你说得对,ThreadLocal 不再是一个选项,因为请求的处理不依赖于特定线程。

目前,Spring 安全性将身份验证信息存储为 ServerWebExchange 属性,因此绑定到当前的 request/response 对。但是当您无法直接访问当前交易所时,您仍然需要该信息,例如 @PreAuthorize.

身份验证信息存储在 Reactive 管道本身中(因此可以从您的 MonoFlux 访问),这是一个非常有趣的 Reactor 功能 - 管理与特定 Subscriber(在 Web 应用程序中,HTTP 客户端从服务器拉取数据并执行此操作)。

我不知道 SecurityContextHolder 的等效方法,或从上下文中获取身份验证信息的一些快捷方法。

查看有关 Reactor Context feature in the reference documentation 的更多信息。 您还可以查看 that being used in Spring Security here.

的示例

我实现了一个 JwtAuthenticationConverter (kotlin):

@Component
class JwtAuthenticationConverter : Function<ServerWebExchange, 
Mono<Authentication>> {

@Autowired
lateinit var jwtTokenUtil: JwtTokenUtil

@Autowired
lateinit var userDetailsService: ReactiveUserDetailsService

private val log = LogFactory.getLog(this::class.java)

override fun apply(exchange: ServerWebExchange): Mono<Authentication> {
    val request = exchange.request

    val token = getJwtFromRequest(request)

    if ( token != null )
        try {
            return userDetailsService.findByUsername(jwtTokenUtil.getUsernameFromToken(token))
                    .map { UsernamePasswordAuthenticationToken(it, null, it.authorities) }
        } catch ( e: Exception ) {
            exchange.response.statusCode = HttpStatus.UNAUTHORIZED
            exchange.response.headers["internal-message"] = e.message
            log.error(e)
        }

    return Mono.empty()
}

private fun getJwtFromRequest(request: ServerHttpRequest): String? {
    val bearerToken = request.headers[SecurityConstants.TOKEN_HEADER]?.first {
        it.startsWith(SecurityConstants.TOKEN_PREFIX, true)}
    return if (bearerToken.isNullOrBlank()) null else bearerToken?.substring(7, bearerToken.length)
}

然后我像这样设置了一个 SecurityConfig:

val authFilter = AuthenticationWebFilter(ReactiveAuthenticationManager {
    authentication: Authentication -> Mono.just(authentication)
})
authFilter.setAuthenticationConverter(jwtAuthenticationConverter)

http.addFilterAt( authFilter, SecurityWebFiltersOrder.AUTHENTICATION)

您可以使用这种方法自定义您的 AuthenticationConverter,就像我对基于 jwt 的身份验证所做的那样,以设置所需的身份验证对象。

ReactiveSecurityContextHolder provides the authentication in a reactive way, and is analogous to SecurityContextHolder.

它的 getContext() method provides a Mono<SecurityContext>, just like SecurityContextHolder.getContext() 提供了 SecurityContext.

ReactiveSecurityContextHolder
                    .getContext()
                    .map(context ->
                            context.getAuthentication()