From 03e2efacf488fb97bff545c1aeb95baf6eb79cc1 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Mon, 30 Sep 2019 13:57:52 -0500 Subject: [PATCH] Add Hello RSocket Sample Fixes gh-7504 --- .../asciidoc/_includes/reactive/rsocket.adoc | 21 ++++ ...-security-samples-boot-hellorsocket.gradle | 11 +++ .../sample/HelloRSocketApplicationITests.java | 97 +++++++++++++++++++ .../java/sample/HelloRSocketApplication.java | 33 +++++++ .../sample/HelloRSocketSecurityConfig.java | 51 ++++++++++ .../main/java/sample/MessageController.java | 34 +++++++ .../src/main/resources/application.properties | 1 + 7 files changed, 248 insertions(+) create mode 100644 samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle create mode 100644 samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java create mode 100644 samples/boot/hellorsocket/src/main/java/sample/HelloRSocketApplication.java create mode 100644 samples/boot/hellorsocket/src/main/java/sample/HelloRSocketSecurityConfig.java create mode 100644 samples/boot/hellorsocket/src/main/java/sample/MessageController.java create mode 100644 samples/boot/hellorsocket/src/main/resources/application.properties diff --git a/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc b/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc index 73b1c2212e..aae35bfbed 100644 --- a/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc +++ b/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 <> and sets up <> 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 diff --git a/samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle b/samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle new file mode 100644 index 0000000000..f2b788a0e2 --- /dev/null +++ b/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' +} diff --git a/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java b/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java new file mode 100644 index 0000000000..1e7e2f360c --- /dev/null +++ b/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 { + private int port; + + @Override + public void onApplicationEvent(RSocketServerInitializedEvent event) { + this.port = event.getrSocketServer().address().getPort(); + } + } +} diff --git a/samples/boot/hellorsocket/src/main/java/sample/HelloRSocketApplication.java b/samples/boot/hellorsocket/src/main/java/sample/HelloRSocketApplication.java new file mode 100644 index 0000000000..fbbc6df27d --- /dev/null +++ b/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); + } + +} diff --git a/samples/boot/hellorsocket/src/main/java/sample/HelloRSocketSecurityConfig.java b/samples/boot/hellorsocket/src/main/java/sample/HelloRSocketSecurityConfig.java new file mode 100644 index 0000000000..057d960251 --- /dev/null +++ b/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); + } +} diff --git a/samples/boot/hellorsocket/src/main/java/sample/MessageController.java b/samples/boot/hellorsocket/src/main/java/sample/MessageController.java new file mode 100644 index 0000000000..0150b3f821 --- /dev/null +++ b/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 message() { + return Mono.just("Hello"); + } +} diff --git a/samples/boot/hellorsocket/src/main/resources/application.properties b/samples/boot/hellorsocket/src/main/resources/application.properties new file mode 100644 index 0000000000..0d91b628f6 --- /dev/null +++ b/samples/boot/hellorsocket/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.rsocket.server.port=8080