skip to content

Explain the difference between securing the SETUP payload and securing REQUEST payloads in RSocket, and how you express each with RSocketSecurity.

level: middleimportance: should knowfreq 35%

answer

  1. SETUP = once per connection; REQUEST = per message
  2. authorizePayload: .setup() / .route() / .anyRequest() / .anyExchange()
  3. first-match-wins, order top-down
  4. setup fail rejects connection; request fail = AccessDenied
  5. per-request metadata vs connection-bound identity

basics

~10 s

The SETUP payload authenticates the connection once, when the client establishes it. REQUEST payloads authenticate individual messages/routes. In the authorizePayload DSL you use .setup() for the connection and .route(...)/.anyRequest() for per-message rules.

solid answer

~40 s

RSocket has two interception points. The SETUP frame is sent once when a client opens a connection; the request frames flow afterwards on that same connection. Spring's PayloadSocketAcceptorInterceptor can enforce security at both. In authorizePayload you write .setup().hasRole("SETUP") (or .authenticated()) to require credentials in the connection's setup metadata, and .route("secure.*").authenticated() / .anyRequest().authenticated() to require credentials or roles on individual message routes. A common pattern is authenticating at SETUP so the whole connection carries an identity, then adding per-route authorization for finer control. You can also authenticate per-request by sending authentication metadata on each request frame. The distinction matters because SETUP auth ties an identity to the connection lifetime, while request auth lets a single connection carry different credentials or leave setup anonymous with only certain routes protected.

code

java · 15 lines
java
@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity security) {
    security.authorizePayload(authorize -> authorize
            // connection handshake: must be authenticated to connect at all
            .setup().authenticated()
            // per-route authorization on request frames
            .route("admin.*").hasRole("ADMIN")
            .route("chat.send").authenticated()
            .route("public.feed").permitAll()
            // fallbacks (order matters: specific rules above)
            .anyRequest().authenticated()
            .anyExchange().permitAll())
        .simpleAuthentication(Customizer.withDefaults());
    return security.build();
}

go deeper

for a junior

Know SETUP happens once at connect time and requests happen after; there are two matchers.

for a middle

Correctly use .setup(), .route(), .anyRequest(), .anyExchange() with proper ordering and pick a strategy.

for a senior

Reason about connection-bound vs per-request identity, failure semantics (connection reject vs AccessDenied), and DoS implications of open setup.

for a principal

Design the auth model for multiplexed/multi-tenant connections and weigh connection-lifetime identity vs per-message credentials across the platform.

RSocket connections have a **two-phase** shape, and Spring Security lets you intercept both: 1. **SETUP phase** — When a requester opens a connection it sends a single **SETUP frame**. That frame can carry *setup metadata* (including authentication metadata) and a *setup data* payload. This happens exactly once per connection. 2. **REQUEST phase** — After setup, the connection carries many **request frames** (request-response, stream, channel, fire-and-forget). Each frame can also carry per-request metadata, including its own authentication metadata and the routing metadata that selects a `@MessageMapping` route. **`PayloadSocketAcceptorInterceptor`** (built from `RSocketSecurity`) is a *payload interceptor* — it runs security logic over these payloads. The `authorizePayload(...)` DSL exposes matchers for each phase: - **`.setup()`** — matches the connection SETUP payload. `.setup().authenticated()` / `.setup().hasRole("SETUP")` forces the client to present valid credentials in setup metadata before the connection is accepted. If it fails, the connection is rejected outright. - **`.route("pattern")`** — matches a request payload whose routing metadata matches the Ant/`PathPattern` (e.g. `"secure.*"`, `"admin.**"`). Used for per-route authorization. - **`.anyRequest()`** — matches any request payload (any route). - **`.anyExchange()`** — matches *any* payload, including SETUP and metadata-push. Broadest matcher; usually put last with `.permitAll()` or `.authenticated()`. **Order matters**: rules are evaluated top-down, first match wins, exactly like HTTP `authorizeHttpRequests`. Put specific `.route(...)` rules before `.anyRequest()`, and `.setup()` before `.anyExchange()`. **Two authentication strategies:** - **Authenticate at SETUP**: credentials go in the setup metadata once. Spring establishes an `Authentication` bound to the connection; subsequent requests inherit it. Cleaner for a stable client identity. You still add per-route authorization for method/role granularity. - **Authenticate per REQUEST**: the setup may be `permitAll`/anonymous, and each request frame carries its own authentication metadata. Useful when one physical connection multiplexes different principals, or credentials rotate. The requester attaches metadata per call via `RSocketRequester.route(...).metadata(credentials, mimeType)`. **Edge cases / gotchas:** - If you only secure `.anyRequest()` but leave `.setup().permitAll()`, unauthenticated clients can open connections and are only stopped when they invoke a protected route. That can be fine, or a DoS/enumeration concern — decide deliberately. - Per-request auth requires the requester to send auth metadata on every call; forgetting it yields access-denied even though setup succeeded. - `.anyExchange()` is broader than `.anyRequest()`; misordering can accidentally permit setup or metadata-push frames. - Authorization failures on a request surface as an `AccessDeniedException` / rejected `Mono`, not a connection teardown; setup failures reject the whole connection. **When to use which:** stable service-to-service identity → authenticate at SETUP; multi-tenant or per-message credentials → authenticate per request; public streams with a few protected routes → open setup + `.route(...).authenticated()`.

  • If setup uses permitAll() but a route requires authentication, what does the client have to do?
    It must attach authentication metadata on that specific request frame (RSocketRequester.route(...).metadata(credentials, authMimeType)); otherwise the request is denied even though the connection was accepted.
  • What's the difference between .anyRequest() and .anyExchange()?
    .anyRequest() matches request-type payloads (actual message routes); .anyExchange() matches any payload including the SETUP and metadata-push frames. .anyExchange() is broader and typically placed last.

saying these in an interview costs you the question

  • Thinking setup and request auth are the same interception point
  • Assuming authenticating at SETUP automatically authorizes every route without route rules
  • Putting .anyRequest()/.anyExchange() before specific .route() rules

context