0

I've created a Spring RSocket server to provide for my kotlin ktor app realtime updates. Without authentication the kotlin client is able to connect to the stream and receive updates. When I configured to the server basic auth using RSocket Spring Security I couldn't make it work on the kotlin client side anymore.

Kotlin side

This is the code I use to request the stream:

var flow = rSocket.requestStream(buildPayload {
    data(ByteArray(0))
    compositeMetadata {
        add(SimpleAuthMetadata("app", "xrOMMKj2jyaf4vH9RC6w"))
        add(RoutingMetadata("api.v1.messages.events.stream/qQYISR9xcmR8ZtVHNgg1lbYgSQcxafPqPW0ZbE0yaA6ham6n54"))
    }
})

flow.onEach {
    println(it.data.readText())
}.collect()

The code above throws the exception io.rsocket.kotlin.RSocketError$Setup$Rejected: Access Denied when requesting the stream.

Obs: After configuring security on the server side, the only change I made to the client was add the SimpleAuthMetadata to the composite metadata. There is no much documentation about it so I don't know if this is right or if there are additional configurations left to do.

The only way I able to make it work on the client side was add to it the spring rsocket dependencies.

RSocketRequester configuration:

@Configuration
class RsocketConfiguration {

    @Bean
    fun rSocketRequester(rsocketRequesterBuilder: RSocketRequester.Builder): RSocketRequester? {
        val authenticationMimeType: MimeType =
            MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
        val credentials = UsernamePasswordMetadata("app", "xrOMMKj2jyaf4vH9RC6w")

        return rsocketRequesterBuilder
            .rsocketStrategies { builder -> builder.encoder(SimpleAuthenticationEncoder()) }
            .setupMetadata(credentials, authenticationMimeType)
            .connectWebSocket(URI.create("ws://localhost:8080/rsocket")).block()
    }
}

Stream request:

scope.launch {
        rSocketRequester
        .route("api.v1.messages.events.stream/{userId}", "qQYISR9xcmR8ZtVHNgg1lbYgSQcxafPqPW0ZbE0yaA6ham6n54")
        .retrieveFlow<EventMessage>()
        .collect {
            println(it.webhookId)
        }
}

Unfortunately, since the consumer(client side) is a ktor app I can't simply add the spring dependencies. Is there a way to make it work using the dependencies provided for kotlin?

1 Answers1

0

Looks like you are authenticating on the SETUP frame level on the Spring Server side, which is fine. You can instruct your pure kotlin rsocket client to send the credentials via SETUP frame:

val connector = RSocketConnector {
        connectionConfig {
            // mime types
            payloadMimeType = PayloadMimeType(
                data = WellKnownMimeType.ApplicationJson,
                metadata = WellKnownMimeType.MessageRSocketCompositeMetadata
            )
            // payload for setup frame
            setupPayload(
                Json.encodeToSetupPayload("client", "password")
            )
        }
    }

encodeToSetupPayload functions:

fun Json.encodeToSetupPayload(username: String, password: String): Payload = buildPayload {
val auth = SimpleAuthMetadata(username, password)
data(ByteReadPacket.Empty)
metadata(CompositeMetadata(auth))}

...and you can create a connection now:

val rsocket = connector.connect(ClientTransport(ServerAddress(8080, TransportType.WS)))
Ivan Dugalic
  • 651
  • 3
  • 10