Spring RSocket over WebSocket - Access user information from HTTP session

612 Views Asked by At

In my web application, users login using a username/password combination and get a session cookie. When initiating a WebSocket connection, I can easily access the user information in the WebSocketHandler, for example:

@Component
public class MyWebSocketHandler implements WebSocketHandler {
    @Override
    public Mono<Void> handle(WebSocketSession session) {
        // two ways to access security context information, either like this:
        Mono<Principal> principal = session.getHandshakeInfo().getPrincipal();

        // or like this
        Mono<SecurityContext> context = ReactiveSecurityContextHolder.getContext();

        //...
        return Mono.empty();
    }
}

Both reuse the HTTP session from the WebSocket handshake, I don't have to send additional authentication over the WebSocket itself. With STOMP the same thing applies: I can just reuse the information of the HTTP session.

How do I achieve the same thing using RSocket? For example, how would I get information about the user inside a MessageMapping method like this?:

@Controller
public class RSocketController {
    @MessageMapping("test-stream")
    public Flux<String> streamTest(RSocketRequester requester) {
        // this mono completes empty, no security context available :(
        Mono<SecurityContext> context = ReactiveSecurityContextHolder.getContext();

        return Flux.empty();
    }
}

I found many resources how to setup authentication with RSocket, but they all rely on an additional authentication after the WebSocket connection is established, but I specifically want to reuse the web session and don't want to send additional tokens over the websocket.

2

There are 2 best solutions below

0
On

You can get the user information using @AuthenticationPrincipal Mono<UserDetails> userDetails.

In case someone use JWT authentication as me you need to add @AuthenticationPrincipal Mono<Jwt> jwt to your method arguments.

But for this to work, you need to configure the RSocketMessageHandler bean, that resolvs the argument.

@Bean
public RSocketMessageHandler rSocketMessageHandler(RSocketStrategies strategies) {
    RSocketMessageHandler handler = new RSocketMessageHandler();
    handler.getArgumentResolverConfigurer()
            .addCustomResolver(new AuthenticationPrincipalArgumentResolver());
    handler.setRSocketStrategies(strategies);
    return handler;
}

Important you have to use org.springframework.security.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver() class as the resolver, and for that you need spring-security-messaging dependency.

3
On

Have you tried the following? I found it in the documentation: 2.2 Secure Your RSocket Methods (might have to scroll down a bit) https://spring.io/blog/2020/06/17/getting-started-with-rsocket-spring-security

@PreAuthorize("hasRole('USER')") // (1)
@MessageMapping("fire-and-forget")
public Mono<Void> fireAndForget(final Message request, @AuthenticationPrincipal UserDetails user) { // (2)
    log.info("Received fire-and-forget request: {}", request);
    log.info("Fire-And-Forget initiated by '{}' in the role '{}'", user.getUsername(), user.getAuthorities());
    return Mono.empty();
}