Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactiveSecurityContextHolder.getContext() is empty but @AuthenticationPrincipal works

I've been using ReactiveAuthenticationManager in Spring Security + Webflux. It is customised to return an instance of UsernamePasswordAuthenticationToken which from what I can tell is an what I should be receiving when I call ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block(). As as far as I can tell I am unable to access the authentication context through both:

SecurityContextHolder.getContext().getAuthentication();

or

ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()

And attempting to access those from controllers or components resolves to null. I had some doubts about whether I am really returning an Authentication instance in my custom manager and it seems like I am:

@Override
public Mono<Authentication> authenticate(final Authentication authentication) {
    if (authentication instanceof PreAuthentication) {
        return Mono.just(authentication)
            .publishOn(Schedulers.parallel())
            .switchIfEmpty(Mono.defer(this::throwCredentialError))
            .cast(PreAuthentication.class)
            .flatMap(this::authenticatePayload)
            .publishOn(Schedulers.parallel())
            .onErrorResume(e -> throwCredentialError())
            .map(userDetails -> new AuthenticationToken(userDetails, userDetails.getAuthorities()));
    }

    return Mono.empty();
}

Where PreAuthentication is an instance of a AbstractAuthenticationToken and AuthenticationToken extends UsernamePasswordAuthenticationToken

Interestingly although ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block() does not work in a controller I can inject the authentication principal with the @AuthenticationPrincipal annotation as a method parameter successfully in a controller.

This seems like a configuration issue but I cannot tell where. Anyone have any idea why I cannot return authentication?

like image 463
DWB Avatar asked Jul 12 '18 22:07

DWB


People also ask

What is SecurityContextHolder Getcontext ()?

The SecurityContext is used to store the details of the currently authenticated user, also known as a principle. So, if you have to get the username or any other user details, you need to get this SecurityContext first. The SecurityContextHolder is a helper class, which provides access to the security context.

What is ReactiveSecurityContextHolder?

Class ReactiveSecurityContextHolderAllows getting and setting the Spring SecurityContext into a Context .

What is @AuthenticationPrincipal?

Annotation Type AuthenticationPrincipal Use AuthenticationPrincipal instead. Annotation that binds a method parameter or method return value to the Authentication. getPrincipal() . This is necessary to signal that the argument should be resolved to the current user rather than a user that might be edited on a form.

How do I return a security context from a reactive-chain?

Return a reactive-chain from method, that is making a ReactiveSecurityContextHolder.getContext () call. Since you are returning a chain of reactive operators, Spring make a subscription to your chain, in order to execute it. When Spring does it, it provides a security context to whole chain.

What happens if the authenticationprincipal is null?

If the Authentication or Authentication.getPrincipal () is null, it will return null. If the types do not match, null will be returned unless AuthenticationPrincipal.errorOnInvalidType () is true in which case a ClassCastException will be thrown.

Why can't I use ThreadLocal objects for authentication in react security?

Because there is no way to use ThreadLocal objects anymore. The only way to get Authentication for you, is to ask for it in controller's method signature, or... Return a reactive-chain from method, that is making a ReactiveSecurityContextHolder.getContext () call.

How do I resolve the customuser argument in the authentication context?

Class AuthenticationPrincipalArgumentResolver will resolve the CustomUser argument using Authentication.getPrincipal () from the SecurityContextHolder. If the Authentication or Authentication.getPrincipal () is null, it will return null.


2 Answers

Since you are using WebFlux, you are handling requests using event-loop. You are not using thread-per-request model anymore, as with Tomcat.

Authentication is stored per context.

With Tomcat, when request arrives, Spring stores authentication in SecurityContextHolder. SecurityContextHolder uses ThreadLocal variable to store authentication. Specific authentication object is visible to you, only if you are trying to fetch it from ThreadLocal object, in the same thread, in which it was set. Thats why you could get authentication in controller via static call. ThreadLocal object knows what to return to you, because it knows your context - your thread.

With WebFlux, you could handle all requests using just 1 thread. Static call like this won't return expected results anymore:

SecurityContextHolder.getContext().getAuthentication();

Because there is no way to use ThreadLocal objects anymore. The only way to get Authentication for you, is to ask for it in controller's method signature, or...

Return a reactive-chain from method, that is making a ReactiveSecurityContextHolder.getContext() call.

Since you are returning a chain of reactive operators, Spring make a subscription to your chain, in order to execute it. When Spring does it, it provides a security context to whole chain. Thus every call ReactiveSecurityContextHolder.getContext() inside this chain, will return expected data.

Your can read more about Mono/Flux context here, because it is a Reactor-specific feature.

like image 104
Alex Derkach Avatar answered Oct 28 '22 11:10

Alex Derkach


I also had a similar problem. so, I uploaded my github for ReactiveSecurityContext with JWT Token example.

https://github.com/heesuk-ahn/spring-webflux-jwt-auth-example

I hope it helps for you.

We need to create a custom authentication filter. If authentication succeeds in that filter, then it stores the principal information in the ReactiveSecurityHolder. We can access the ReactiveSecurityHolder from the controller.

like image 31
ahn heesuk Avatar answered Oct 28 '22 11:10

ahn heesuk