@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 管道本身中(因此可以从您的 Mono
或 Flux
访问),这是一个非常有趣的 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()
您能否解释处理 @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 管道本身中(因此可以从您的 Mono
或 Flux
访问),这是一个非常有趣的 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()