Reactive access to SecurityContext

In order to access the SecurityContext in the new reactive Spring Security module, we have a new class called ReactiveSecurityContextHolder.

ReactiveSecurityContextHolder provides access to the current SecurityContext in a reactive manner over a static getContext method, which returns Mono<SecurityContext>. This means that we can write the following code in order to access SecurityContext in the application:

@RestController                                                    // (1)
@RequestMapping("/api/v1") //
public class SecuredProfileController { //

@GetMapping("/profiles") // (2)
@PreAuthorize("hasRole(USER)") // (2.1)
public Mono<Profile> getProfile() { // (2.2)
return ReactiveSecurityContextHolder // (2.3)
.getContext() // (2.4)
.map(SecurityContext::getAuthentication) //
.flatMap(auth -> //
profileService.getByUser(auth.getName()) // (2.5)
); //
} //
}

The preceding example may be explained as follows:

  1. This is the declaration of the REST controller class, with the request mapping equal to "/api/v1".

  2. This is the getProfile handler method declaration. As we can see, this method returns the Mono reactive type, which allows reactive access to the data, as shown at point (2.2). Then, in order to access the current SecurityContext, we call ReactiveSecurityContextHolder.getContext(), as shown in points (2.3) and (2.4). Finally, if SecurityContext is present, flatMap is handled and we may access the user's profile, as shown at point 2.5. In addition, this method is annotated with @PreAuthorize, which in this case checks that the available Authentication has the required role. Note that if we have a reactive return type, the invocation of the method will be deferred until the required Authentication is resolved and the required authorities are present.

As we can see, the API of the new reactive context holder is somewhat similar to that which we have in a synchronous counterpart of the API. Moreover, with the new generation of Spring Security, we can use the same annotations in order to check the required authorities.

Internally, ReactiveSecurityContextHolder relies on the Reactor Context API. The current information about the logged-in user is held within the instance of the Context interface. The following example shows how ReactiveSecurityContextHolder works under the hood:

static final Class<?> SECURITY_CONTEXT_KEY = SecurityContext.class;
...
public static
Mono<SecurityContext> getContext() {
return Mono.subscriberContext()
.filter(c -> c.hasKey(SECURITY_CONTEXT_KEY))
.flatMap(c -> c.<Mono<SecurityContext>>get(SECURITY_CONTEXT_KEY));
}

As we may remember from Chapter 4, Project Reactor - the Foundation for Reactive Apps, in order to access the internal Reactor Context, we can use the dedicated operator of the Mono reactive type, called subscriberContext. Then, once the context is accessed, we filter the current Context and check whether it contains a specific key. The value hidden in that key is a Mono from the SecurityContext, which means that we can access the current SecurityContext in a reactive way. The execution is related to retrieving the stored SecurityContext from, for instance, a database, which is executed only when someone subscribes to the given Mono.

Even though the API of ReactiveSecurityContextHolder looks familiar, it hides a lot of pitfalls. For example, by mistake, we may follow the practice we got used to when working with SecurityContextHolder. Therefore, we might blindly implement the common interaction depicted in the following code sample:

ReactiveSecurityContextHolder
.getContext()
.map(SecurityContext::getAuthentication)
.block();

Just like we used to retrieve the SecurityContext from ThreadLocal, we may be tempted to try to do the same with ReactiveSecurityContextHolder, as shown in the previous example. Unfortunately, when we make a call to getContext and subscribe to the stream using the block method, an empty context will be configured in the stream. Thus, once the ReactiveSecurityContextHodler class tries to access the inner Context, no available SecurityContext will be found there.

So, the question is, how can the Context be set up and made accessible when we connect the streams properly, as shown at the beginning of the section? The answer lies in the new ReactorContextWebFilter from the fifth generation of the Spring Security module. During the invocation, ReactorContextWebFilter provides a Reactor Context using the subscriberContext method. In addition, the resolution of the SecurityContext is carried out using the ServerSecurityContextRepository. ServerSecurityContextRepository has two methods, called save and load:

interface ServerSecurityContextRepository {

Mono<Void> save(ServerWebExchange exchange, SecurityContext context);

Mono<SecurityContext> load(ServerWebExchange exchange);
}

As we can see in the preceding code, the save method allows to associate the SecurityContext with a specific ServerWebExchange and then restore it using the load method from the incoming user request attached to the ServerWebExchange.

As we can see, the main advantage of the new generation of Spring Security is full-fledged support for reactive access to SecurityContext. Here, reactive access means that the actual SecurityContext may be stored in a database, so the resolution of the stored SecurityContext does not require blocking operations. The strategy of context resolution is lazy, so the actual call to the underlying storage is executed only when we subscribe to ReactiveSecurityContextHolder.getContext(). Finally, the mechanism of the SecurityContext transfer allows us to build complex streaming processes easily, paying no attention to the problem of common ThreadLocal propagation between Thread instances.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.139.8