2

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.

CalibeR.50
  • 370
  • 2
  • 13

2 Answers2

0

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();
}
Tom Cools
  • 1,098
  • 8
  • 23
  • Appreciate your answer, but unfortunately this only works if you authenticate AFTER initiating the connection, for example in the setup frame initiating the rsocket connection. I want to reuse the authentication information available when making the websocket handshake. You can actually see they are doing exactly this in the link you posted, few lines below the code snipped they do setupMetadata(user, SIMPLE_AUTH), which sends authentication information in the rsocket setup frame. – CalibeR.50 Jan 25 '22 at 13:25
  • Normally the "Message" class for each message should contain a unique identifier for each connection. You can keep a map (in memory, or in a DB) to match the authentication detail sent in the setup frame with a session. (at least, that's how i've done it in the past... don' thave code on hand tho..) – Tom Cools Jan 25 '22 at 14:15
  • I don't want to send additional authentication information in the setup frame. I want to reuse the HTTP session which was used to initiate the websocket connection (transport layer), just like the example with the raw websockets. Sending the authentication details in the setup frame would require to have the authentication details (or some token more realisticly) accessible in JavaScript, while I try to get it work with a http-only cookie (no token or authentication details available in javascript). – CalibeR.50 Jan 25 '22 at 14:31
0

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.

Gergo
  • 230
  • 2
  • 11