Skip to content

Commit 03e2efa

Browse files
committed
Add Hello RSocket Sample
Fixes gh-7504
1 parent 83b5f5c commit 03e2efa

File tree

7 files changed

+248
-0
lines changed

7 files changed

+248
-0
lines changed

docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
Spring Security's RSocket support relies on a `SocketAcceptorInterceptor`.
55
The main entry point into security is found in the `PayloadSocketAcceptorInterceptor` which adapts the RSocket APIs to allow intercepting a `PayloadExchange` with `PayloadInterceptor` implementations.
66

7+
You can find a few sample applications that demonstrate the code below:
8+
9+
* Hello RSocket {gh-samples-url}/boot/hellorsocket[hellorsocket]
10+
* https://github.com/rwinch/spring-flights/tree/security[Spring Flights]
11+
12+
713
== Minimal RSocket Security Configuration
814

915
You can find a minimal RSocket Security configuration below:
@@ -28,6 +34,21 @@ public class HelloRSocketSecurityConfig {
2834

2935
This configuration enables <<rsocket-authentication-basic,basic authentication>> and sets up <<authorization,rsocket-authorization>> to require an authenticated user for any request.
3036

37+
== Adding SecuritySocketAcceptorInterceptor
38+
39+
For Spring Security to work we need to apply `SecuritySocketAcceptorInterceptor` to the `ServerRSocketFactory`.
40+
This is what connects our `PayloadSocketAcceptorInterceptor` we created with the RSocket infrastructure.
41+
In a Spring Boot application this can be done using the following code.
42+
43+
[source,java]
44+
----
45+
@Bean
46+
ServerRSocketFactoryCustomizer springSecurityRSocketSecurity(
47+
SecuritySocketAcceptorInterceptor interceptor) {
48+
return builder -> builder.addSocketAcceptorPlugin(interceptor);
49+
}
50+
----
51+
3152
[[rsocket-authentication]]
3253
== RSocket Authentication
3354

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apply plugin: 'io.spring.convention.spring-sample-boot'
2+
3+
dependencies {
4+
compile project(':spring-security-core')
5+
compile project(':spring-security-config')
6+
compile project(':spring-security-rsocket')
7+
compile 'org.springframework.boot:spring-boot-starter-rsocket'
8+
9+
testCompile project(':spring-security-test')
10+
testCompile 'org.springframework.boot:spring-boot-starter-test'
11+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package sample;
17+
18+
import org.junit.Test;
19+
import org.junit.runner.RunWith;
20+
import org.springframework.beans.factory.annotation.Autowired;
21+
import org.springframework.boot.rsocket.context.RSocketServerInitializedEvent;
22+
import org.springframework.boot.test.context.SpringBootTest;
23+
import org.springframework.boot.test.context.TestConfiguration;
24+
import org.springframework.context.ApplicationListener;
25+
import org.springframework.messaging.rsocket.RSocketRequester;
26+
import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
27+
import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
28+
import org.springframework.test.context.TestPropertySource;
29+
import org.springframework.test.context.junit4.SpringRunner;
30+
import reactor.core.publisher.Mono;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
34+
import static org.springframework.security.rsocket.metadata.UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE;
35+
36+
/**
37+
* @author Rob Winch
38+
* @since 5.0
39+
*/
40+
@RunWith(SpringRunner.class)
41+
@TestPropertySource(properties = "spring.rsocket.server.port=0")
42+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
43+
public class HelloRSocketApplicationITests {
44+
45+
@Autowired
46+
RSocketRequester.Builder requester;
47+
48+
@Test
49+
public void messageWhenAuthenticatedThenSuccess() {
50+
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
51+
RSocketRequester requester = this.requester
52+
.rsocketStrategies(builder -> builder.encoder(new BasicAuthenticationEncoder()))
53+
.setupMetadata(credentials, BASIC_AUTHENTICATION_MIME_TYPE)
54+
.connectTcp("localhost", getPort())
55+
.block();
56+
57+
String message = requester.route("message")
58+
.data(Mono.empty())
59+
.retrieveMono(String.class)
60+
.block();
61+
62+
assertThat(message).isEqualTo("Hello");
63+
}
64+
65+
@Test
66+
public void messageWhenNotAuthenticatedThenError() {
67+
RSocketRequester requester = this.requester
68+
.connectTcp("localhost", getPort())
69+
.block();
70+
71+
assertThatThrownBy(() -> requester.route("message")
72+
.data(Mono.empty())
73+
.retrieveMono(String.class)
74+
.block())
75+
.isNotNull();
76+
}
77+
78+
// FIXME: Waiting for @LocalRSocketServerPort
79+
// https://github.com/spring-projects/spring-boot/pull/18287
80+
81+
@Autowired
82+
Config config;
83+
84+
private int getPort() {
85+
return this.config.port;
86+
}
87+
88+
@TestConfiguration
89+
static class Config implements ApplicationListener<RSocketServerInitializedEvent> {
90+
private int port;
91+
92+
@Override
93+
public void onApplicationEvent(RSocketServerInitializedEvent event) {
94+
this.port = event.getrSocketServer().address().getPort();
95+
}
96+
}
97+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package sample;
18+
19+
import org.springframework.boot.SpringApplication;
20+
import org.springframework.boot.autoconfigure.SpringBootApplication;
21+
22+
/**
23+
* @author Rob Winch
24+
* @since 5.2
25+
*/
26+
@SpringBootApplication
27+
public class HelloRSocketApplication {
28+
29+
public static void main(String[] args) {
30+
SpringApplication.run(HelloRSocketApplication.class, args);
31+
}
32+
33+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package sample;
18+
19+
import org.springframework.boot.rsocket.server.ServerRSocketFactoryCustomizer;
20+
import org.springframework.context.annotation.Bean;
21+
import org.springframework.context.annotation.Configuration;
22+
import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity;
23+
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
24+
import org.springframework.security.core.userdetails.User;
25+
import org.springframework.security.core.userdetails.UserDetails;
26+
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
27+
28+
/**
29+
* @author Rob Winch
30+
* @since 5.2
31+
*/
32+
@Configuration
33+
@EnableRSocketSecurity
34+
public class HelloRSocketSecurityConfig {
35+
36+
@Bean
37+
MapReactiveUserDetailsService userDetailsService() {
38+
UserDetails user = User.withDefaultPasswordEncoder()
39+
.username("user")
40+
.password("password")
41+
.roles("SETUP")
42+
.build();
43+
return new MapReactiveUserDetailsService(user);
44+
}
45+
46+
@Bean
47+
ServerRSocketFactoryCustomizer springSecurityRSocketSecurity(
48+
SecuritySocketAcceptorInterceptor interceptor) {
49+
return builder -> builder.addSocketAcceptorPlugin(interceptor);
50+
}
51+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package sample;
18+
19+
import org.springframework.messaging.handler.annotation.MessageMapping;
20+
import org.springframework.stereotype.Controller;
21+
import reactor.core.publisher.Mono;
22+
23+
/**
24+
* @author Rob Winch
25+
* @since 5.2
26+
*/
27+
@Controller
28+
public class MessageController {
29+
30+
@MessageMapping("message")
31+
public Mono<String> message() {
32+
return Mono.just("Hello");
33+
}
34+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
spring.rsocket.server.port=8080

0 commit comments

Comments
 (0)