Browse Source

Add Hello RSocket Sample

Fixes gh-7504
Rob Winch 5 years ago
parent
commit
03e2efacf4

+ 21 - 0
docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc

@@ -4,6 +4,12 @@
 Spring Security's RSocket support relies on a `SocketAcceptorInterceptor`.
 The main entry point into security is found in the `PayloadSocketAcceptorInterceptor` which adapts the RSocket APIs to allow intercepting a `PayloadExchange` with `PayloadInterceptor` implementations.
 
+You can find a few sample applications that demonstrate the code below:
+
+* Hello RSocket {gh-samples-url}/boot/hellorsocket[hellorsocket]
+* https://github.com/rwinch/spring-flights/tree/security[Spring Flights]
+
+
 == Minimal RSocket Security Configuration
 
 You can find a minimal RSocket Security configuration below:
@@ -28,6 +34,21 @@ public class HelloRSocketSecurityConfig {
 
 This configuration enables <<rsocket-authentication-basic,basic authentication>> and sets up <<authorization,rsocket-authorization>> to require an authenticated user for any request.
 
+== Adding SecuritySocketAcceptorInterceptor
+
+For Spring Security to work we need to apply `SecuritySocketAcceptorInterceptor` to the `ServerRSocketFactory`.
+This is what connects our `PayloadSocketAcceptorInterceptor` we created with the RSocket infrastructure.
+In a Spring Boot application this can be done using the following code.
+
+[source,java]
+----
+@Bean
+ServerRSocketFactoryCustomizer springSecurityRSocketSecurity(
+		SecuritySocketAcceptorInterceptor interceptor) {
+	return builder -> builder.addSocketAcceptorPlugin(interceptor);
+}
+----
+
 [[rsocket-authentication]]
 == RSocket Authentication
 

+ 11 - 0
samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle

@@ -0,0 +1,11 @@
+apply plugin: 'io.spring.convention.spring-sample-boot'
+
+dependencies {
+	compile project(':spring-security-core')
+	compile project(':spring-security-config')
+	compile project(':spring-security-rsocket')
+	compile 'org.springframework.boot:spring-boot-starter-rsocket'
+
+	testCompile project(':spring-security-test')
+	testCompile 'org.springframework.boot:spring-boot-starter-test'
+}

+ 97 - 0
samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java

@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2017 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 sample;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.rsocket.context.RSocketServerInitializedEvent;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.ApplicationListener;
+import org.springframework.messaging.rsocket.RSocketRequester;
+import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
+import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+import reactor.core.publisher.Mono;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.springframework.security.rsocket.metadata.UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+@RunWith(SpringRunner.class)
+@TestPropertySource(properties = "spring.rsocket.server.port=0")
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class HelloRSocketApplicationITests {
+
+	@Autowired
+	RSocketRequester.Builder requester;
+
+	@Test
+	public void messageWhenAuthenticatedThenSuccess() {
+		UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
+		RSocketRequester requester = this.requester
+				.rsocketStrategies(builder -> builder.encoder(new BasicAuthenticationEncoder()))
+				.setupMetadata(credentials, BASIC_AUTHENTICATION_MIME_TYPE)
+				.connectTcp("localhost", getPort())
+				.block();
+
+		String message = requester.route("message")
+				.data(Mono.empty())
+				.retrieveMono(String.class)
+				.block();
+
+		assertThat(message).isEqualTo("Hello");
+	}
+
+	@Test
+	public void messageWhenNotAuthenticatedThenError() {
+		RSocketRequester requester = this.requester
+				.connectTcp("localhost", getPort())
+				.block();
+
+		assertThatThrownBy(() -> requester.route("message")
+				.data(Mono.empty())
+				.retrieveMono(String.class)
+				.block())
+				.isNotNull();
+	}
+
+	// FIXME: Waiting for @LocalRSocketServerPort
+	// https://github.com/spring-projects/spring-boot/pull/18287
+
+	@Autowired
+	Config config;
+
+	private int getPort() {
+		return this.config.port;
+	}
+
+	@TestConfiguration
+	static class Config implements ApplicationListener<RSocketServerInitializedEvent> {
+		private int port;
+
+		@Override
+		public void onApplicationEvent(RSocketServerInitializedEvent event) {
+			this.port = event.getrSocketServer().address().getPort();
+		}
+	}
+}

+ 33 - 0
samples/boot/hellorsocket/src/main/java/sample/HelloRSocketApplication.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2019 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 sample;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author Rob Winch
+ * @since 5.2
+ */
+@SpringBootApplication
+public class HelloRSocketApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(HelloRSocketApplication.class, args);
+	}
+
+}

+ 51 - 0
samples/boot/hellorsocket/src/main/java/sample/HelloRSocketSecurityConfig.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2017 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 sample;
+
+import org.springframework.boot.rsocket.server.ServerRSocketFactoryCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
+
+/**
+ * @author Rob Winch
+ * @since 5.2
+ */
+@Configuration
+@EnableRSocketSecurity
+public class HelloRSocketSecurityConfig {
+
+	@Bean
+	MapReactiveUserDetailsService userDetailsService() {
+		UserDetails user = User.withDefaultPasswordEncoder()
+			.username("user")
+			.password("password")
+			.roles("SETUP")
+			.build();
+		return new MapReactiveUserDetailsService(user);
+	}
+
+	@Bean
+	ServerRSocketFactoryCustomizer springSecurityRSocketSecurity(
+			SecuritySocketAcceptorInterceptor interceptor) {
+		return builder -> builder.addSocketAcceptorPlugin(interceptor);
+	}
+}

+ 34 - 0
samples/boot/hellorsocket/src/main/java/sample/MessageController.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2019 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 sample;
+
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.stereotype.Controller;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author Rob Winch
+ * @since 5.2
+ */
+@Controller
+public class MessageController {
+
+	@MessageMapping("message")
+	public Mono<String> message() {
+		return Mono.just("Hello");
+	}
+}

+ 1 - 0
samples/boot/hellorsocket/src/main/resources/application.properties

@@ -0,0 +1 @@
+spring.rsocket.server.port=8080