Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get SecurityContext in spring security webflux [duplicate]

Can you explain where the advice handling @PreAuthorize("hasRole('ADMIN')") retrieves the SecurityContext in a Reactive application?

The following Spring Security example is a good illustration of this kind of usage: https://github.com/spring-projects/spring-security/tree/5.0.0.M4/samples/javaconfig/hellowebflux-method

After checking the Spring Security Webflux source code, I've found some implementations of SecurityContextRepository but the load method needs the ServerWebExchange as a parameter.

I'm trying to understand how to replace SecurityContextHolder.getContext().getAuthentication() call in a standard service (because ThreadLocal is no longer an option in a Reactive Application), but I don't understand how to replace this with a call to a SecurityContextRepository without a reference on the ServerWebExchange.

like image 660
etiennepeiniau Avatar asked Oct 17 '17 14:10

etiennepeiniau


People also ask

Can I have multiple WebSecurityConfigurerAdapter?

When using Java configuration, the way to define multiple security realms is to have multiple @Configuration classes that extend the WebSecurityConfigurerAdapter base class – each with its own security configuration. These classes can be static and placed inside the main config.

What is ServerSecurityContextRepository?

Interface ServerSecurityContextRepository Strategy used for persisting a SecurityContext between requests.

What is backpressure in spring WebFlux?

3. Handling Backpressure in Spring WebFlux. Spring WebFlux provides an asynchronous non-blocking flow of reactive streams. The responsible for backpressure within Spring WebFlux is the Project Reactor. It internally uses Flux functionalities to apply the mechanisms to control the events produced by the emitter.

What is AuthenticationEntryPoint in Spring Security?

AuthenticationEntryPoint is used to send an HTTP response that requests credentials from a client. Sometimes a client will proactively include credentials such as a username/password to request a resource.


3 Answers

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

Its getContext() method provides a Mono<SecurityContext>, just like SecurityContextHolder.getContext() provides a SecurityContext.

ReactiveSecurityContextHolder
                    .getContext()
                    .map(context ->
                            context.getAuthentication()
like image 127
Niklas Eldberger Avatar answered Oct 31 '22 05:10

Niklas Eldberger


You're right, ThreadLocal is no longer an option because the processing of a request is not tied to a particular thread.

Currently, Spring Security is storing the authentication information as a ServerWebExchange attribute, so tied to the current request/response pair. But you still need that information when you don't have direct access to the current exchange, like @PreAuthorize.

The authentication information is stored in the Reactive pipeline itself (so accessible from your Mono or Flux), which is a very interesting Reactor feature - managing a context tied to a particular Subscriber (in a web application, the HTTP client is pulling data from the server and acts as such).

I'm not aware of an equivalent of SecurityContextHolder, or some shortcut method to get the Authentication information from the context.

See more about Reactor Context feature in the reference documentation. You can also see an example of that being used in Spring Security here.

like image 39
Brian Clozel Avatar answered Oct 31 '22 05:10

Brian Clozel


I implemented a 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)
}

And then I set a SecurityConfig like this:

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

http.addFilterAt( authFilter, SecurityWebFiltersOrder.AUTHENTICATION)

You can use this approach to customize your AuthenticationConverter as I did to jwt based authentication to set the desired authentication object.

like image 27
Tiago Peixoto Avatar answered Oct 31 '22 03:10

Tiago Peixoto