Add Disabling Anonymous Authentication in RSocketSecurity

Closes: gh-17132

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>

1

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>

1

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
This commit is contained in:
Andrey Litvitski 2025-08-05 00:12:02 +03:00 committed by Josh Cummings
parent 3278f3a410
commit 559b73b39f
2 changed files with 186 additions and 7 deletions

View File

@ -0,0 +1,147 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.rsocket;
import java.util.ArrayList;
import java.util.List;
import io.rsocket.core.RSocketServer;
import io.rsocket.exceptions.RejectedSetupException;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.transport.netty.server.CloseableChannel;
import io.rsocket.transport.netty.server.TcpServerTransport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
import org.springframework.security.rsocket.util.matcher.PayloadExchangeAuthorizationContext;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Andrey Litvitski
*/
@ContextConfiguration
@ExtendWith(SpringExtension.class)
public class AnonymousAuthenticationITests {
@Autowired
RSocketMessageHandler handler;
@Autowired
SecuritySocketAcceptorInterceptor interceptor;
@Autowired
ServerController controller;
private CloseableChannel server;
private RSocketRequester requester;
@BeforeEach
public void setup() {
// @formatter:off
this.server = RSocketServer.create()
.payloadDecoder(PayloadDecoder.ZERO_COPY)
.interceptors((registry) -> registry.forSocketAcceptor(this.interceptor)
)
.acceptor(this.handler.responder())
.bind(TcpServerTransport.create("localhost", 0))
.block();
// @formatter:on
}
@AfterEach
public void dispose() {
this.requester.rsocket().dispose();
this.server.dispose();
this.controller.payloads.clear();
}
@Test
public void requestWhenAnonymousDisabledThenRespondsWithForbidden() {
this.requester = RSocketRequester.builder()
.rsocketStrategies(this.handler.getRSocketStrategies())
.connectTcp("localhost", this.server.address().getPort())
.block();
String data = "andrew";
assertThatExceptionOfType(RejectedSetupException.class).isThrownBy(
() -> this.requester.route("secure.retrieve-mono").data(data).retrieveMono(String.class).block());
assertThat(this.controller.payloads).isEmpty();
}
@Configuration
@EnableRSocketSecurity
static class Config {
@Bean
ServerController controller() {
return new ServerController();
}
@Bean
RSocketMessageHandler messageHandler() {
return new RSocketMessageHandler();
}
@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
ReactiveAuthorizationManager<PayloadExchangeAuthorizationContext> anonymous = (authentication,
exchange) -> authentication.map(trustResolver::isAnonymous).map(AuthorizationDecision::new);
rsocket.authorizePayload((authorize) -> authorize.anyExchange().access(anonymous));
rsocket.anonymousAuthentication((anonymousAuthentication) -> anonymousAuthentication.disable());
return rsocket.build();
}
}
@Controller
static class ServerController {
private List<String> payloads = new ArrayList<>();
@MessageMapping("**")
String retrieveMono(String payload) {
add(payload);
return "Hi " + payload;
}
private void add(String p) {
this.payloads.add(p);
}
}
}

View File

@ -109,6 +109,7 @@ import org.springframework.security.rsocket.util.matcher.RoutePayloadExchangeMat
* @author Manuel Tejeda
* @author Ebert Toribio
* @author Ngoc Nhan
* @author Andrey Litvitski
* @since 5.2
*/
public class RSocketSecurity {
@ -119,6 +120,8 @@ public class RSocketSecurity {
private SimpleAuthenticationSpec simpleAuthSpec;
private AnonymousAuthenticationSpec anonymousAuthSpec = new AnonymousAuthenticationSpec(this);
private JwtSpec jwtSpec;
private AuthorizePayloadsSpec authorizePayload;
@ -164,6 +167,19 @@ public class RSocketSecurity {
return this;
}
/**
* Adds anonymous authentication
* @param anonymous a customizer
* @return this instance
*/
public RSocketSecurity anonymousAuthentication(Customizer<AnonymousAuthenticationSpec> anonymous) {
if (this.anonymousAuthSpec == null) {
this.anonymousAuthSpec = new AnonymousAuthenticationSpec(this);
}
anonymous.customize(this.anonymousAuthSpec);
return this;
}
/**
* Adds authentication with BasicAuthenticationPayloadExchangeConverter.
* @param basic
@ -214,7 +230,9 @@ public class RSocketSecurity {
if (this.jwtSpec != null) {
result.addAll(this.jwtSpec.build());
}
result.add(anonymous());
if (this.anonymousAuthSpec != null) {
result.add(this.anonymousAuthSpec.build());
}
if (this.authorizePayload != null) {
result.add(this.authorizePayload.build());
}
@ -222,12 +240,6 @@ public class RSocketSecurity {
return result;
}
private AnonymousPayloadInterceptor anonymous() {
AnonymousPayloadInterceptor result = new AnonymousPayloadInterceptor("anonymousUser");
result.setOrder(PayloadInterceptorOrder.ANONYMOUS.getOrder());
return result;
}
private <T> T getBean(Class<T> beanClass) {
if (this.context == null) {
return null;
@ -283,6 +295,26 @@ public class RSocketSecurity {
}
public final class AnonymousAuthenticationSpec {
private RSocketSecurity parent;
private AnonymousAuthenticationSpec(RSocketSecurity parent) {
this.parent = parent;
}
protected AnonymousPayloadInterceptor build() {
AnonymousPayloadInterceptor result = new AnonymousPayloadInterceptor("anonymousUser");
result.setOrder(PayloadInterceptorOrder.ANONYMOUS.getOrder());
return result;
}
public void disable() {
this.parent.anonymousAuthSpec = null;
}
}
public final class BasicAuthenticationSpec {
private ReactiveAuthenticationManager authenticationManager;