Browse Source

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>
Andrey Litvitski 1 month ago
parent
commit
559b73b39f

+ 147 - 0
config/src/integration-test/java/org/springframework/security/config/annotation/rsocket/AnonymousAuthenticationITests.java

@@ -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);
+		}
+
+	}
+
+}

+ 39 - 7
config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurity.java

@@ -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;