Skip to content

Commit 1d7208f

Browse files
committed
Add RSocket Authentication Extension Support
Fixes gh-7935
1 parent 209c81d commit 1d7208f

File tree

13 files changed

+593
-31
lines changed

13 files changed

+593
-31
lines changed

config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurity.java

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
3131
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
3232
import org.springframework.security.rsocket.api.PayloadInterceptor;
33+
import org.springframework.security.rsocket.authentication.AuthenticationPayloadExchangeConverter;
3334
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
3435
import org.springframework.security.rsocket.authentication.AnonymousPayloadInterceptor;
3536
import org.springframework.security.rsocket.authentication.AuthenticationPayloadInterceptor;
@@ -44,6 +45,7 @@
4445
import reactor.core.publisher.Mono;
4546

4647
import java.util.ArrayList;
48+
import java.util.Arrays;
4749
import java.util.List;
4850

4951
/**
@@ -116,6 +118,8 @@ public class RSocketSecurity {
116118

117119
private BasicAuthenticationSpec basicAuthSpec;
118120

121+
private SimpleAuthenticationSpec simpleAuthSpec;
122+
119123
private JwtSpec jwtSpec;
120124

121125
private AuthorizePayloadsSpec authorizePayload;
@@ -145,6 +149,58 @@ public RSocketSecurity authenticationManager(ReactiveAuthenticationManager authe
145149
return this;
146150
}
147151

152+
/**
153+
* Adds support for validating a username and password using
154+
* <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Simple.md">Simple Authentication</a>
155+
* @param simple a customizer
156+
* @return RSocketSecurity for additional configuration
157+
* @since 5.3
158+
*/
159+
public RSocketSecurity simpleAuthentication(Customizer<SimpleAuthenticationSpec> simple) {
160+
if (this.simpleAuthSpec == null) {
161+
this.simpleAuthSpec = new SimpleAuthenticationSpec();
162+
}
163+
simple.customize(this.simpleAuthSpec);
164+
return this;
165+
}
166+
167+
/**
168+
* @since 5.3
169+
*/
170+
public class SimpleAuthenticationSpec {
171+
private ReactiveAuthenticationManager authenticationManager;
172+
173+
public SimpleAuthenticationSpec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
174+
this.authenticationManager = authenticationManager;
175+
return this;
176+
}
177+
178+
private ReactiveAuthenticationManager getAuthenticationManager() {
179+
if (this.authenticationManager == null) {
180+
return RSocketSecurity.this.authenticationManager;
181+
}
182+
return this.authenticationManager;
183+
}
184+
185+
protected AuthenticationPayloadInterceptor build() {
186+
ReactiveAuthenticationManager manager = getAuthenticationManager();
187+
AuthenticationPayloadInterceptor result = new AuthenticationPayloadInterceptor(manager);
188+
result.setAuthenticationConverter(new AuthenticationPayloadExchangeConverter());
189+
result.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
190+
return result;
191+
}
192+
193+
private SimpleAuthenticationSpec() {}
194+
}
195+
196+
/**
197+
* Adds authentication with BasicAuthenticationPayloadExchangeConverter.
198+
*
199+
* @param basic
200+
* @return
201+
* @deprecated Use {@link #simpleAuthentication(Customizer)}
202+
*/
203+
@Deprecated
148204
public RSocketSecurity basicAuthentication(Customizer<BasicAuthenticationSpec> basic) {
149205
if (this.basicAuthSpec == null) {
150206
this.basicAuthSpec = new BasicAuthenticationSpec();
@@ -206,12 +262,17 @@ private ReactiveAuthenticationManager getAuthenticationManager() {
206262
return RSocketSecurity.this.authenticationManager;
207263
}
208264

209-
protected AuthenticationPayloadInterceptor build() {
265+
protected List<AuthenticationPayloadInterceptor> build() {
210266
ReactiveAuthenticationManager manager = getAuthenticationManager();
211-
AuthenticationPayloadInterceptor result = new AuthenticationPayloadInterceptor(manager);
212-
result.setAuthenticationConverter(new BearerPayloadExchangeConverter());
213-
result.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
214-
return result;
267+
AuthenticationPayloadInterceptor legacy = new AuthenticationPayloadInterceptor(manager);
268+
legacy.setAuthenticationConverter(new BearerPayloadExchangeConverter());
269+
legacy.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
270+
271+
AuthenticationPayloadInterceptor standard = new AuthenticationPayloadInterceptor(manager);
272+
standard.setAuthenticationConverter(new AuthenticationPayloadExchangeConverter());
273+
standard.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
274+
275+
return Arrays.asList(standard, legacy);
215276
}
216277

217278
private JwtSpec() {}
@@ -240,8 +301,11 @@ private List<PayloadInterceptor> payloadInterceptors() {
240301
if (this.basicAuthSpec != null) {
241302
result.add(this.basicAuthSpec.build());
242303
}
304+
if (this.simpleAuthSpec != null) {
305+
result.add(this.simpleAuthSpec.build());
306+
}
243307
if (this.jwtSpec != null) {
244-
result.add(this.jwtSpec.build());
308+
result.addAll(this.jwtSpec.build());
245309
}
246310
result.add(anonymous());
247311

config/src/main/java/org/springframework/security/config/annotation/rsocket/SecuritySocketAcceptorInterceptorConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ private PayloadSocketAcceptorInterceptor defaultInterceptor(
4747
}
4848
rsocket
4949
.basicAuthentication(Customizer.withDefaults())
50+
.simpleAuthentication(Customizer.withDefaults())
5051
.authorizePayload(authz ->
5152
authz
5253
.setup().authenticated()

config/src/test/java/org/springframework/security/config/annotation/rsocket/JwtITests.java

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@
1515
*/
1616
package org.springframework.security.config.annotation.rsocket;
1717

18-
import java.util.ArrayList;
19-
import java.util.Arrays;
20-
import java.util.List;
21-
2218
import io.rsocket.RSocketFactory;
2319
import io.rsocket.frame.decoder.PayloadDecoder;
2420
import io.rsocket.transport.netty.server.CloseableChannel;
@@ -27,8 +23,6 @@
2723
import org.junit.Before;
2824
import org.junit.Test;
2925
import org.junit.runner.RunWith;
30-
import reactor.core.publisher.Mono;
31-
3226
import org.springframework.beans.factory.annotation.Autowired;
3327
import org.springframework.context.annotation.Bean;
3428
import org.springframework.context.annotation.Configuration;
@@ -43,12 +37,20 @@
4337
import org.springframework.security.oauth2.jwt.TestJwts;
4438
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
4539
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
46-
import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
40+
import org.springframework.security.rsocket.metadata.BearerTokenAuthenticationEncoder;
4741
import org.springframework.security.rsocket.metadata.BearerTokenMetadata;
4842
import org.springframework.stereotype.Controller;
4943
import org.springframework.test.context.ContextConfiguration;
5044
import org.springframework.test.context.junit4.SpringRunner;
45+
import org.springframework.util.MimeType;
46+
import org.springframework.util.MimeTypeUtils;
47+
import reactor.core.publisher.Mono;
5148

49+
import java.util.ArrayList;
50+
import java.util.Arrays;
51+
import java.util.List;
52+
53+
import static io.rsocket.metadata.WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION;
5254
import static org.assertj.core.api.Assertions.assertThat;
5355
import static org.mockito.Matchers.any;
5456
import static org.mockito.Mockito.mock;
@@ -95,7 +97,7 @@ public void dispose() {
9597
}
9698

9799
@Test
98-
public void routeWhenAuthorized() {
100+
public void routeWhenBearerThenAuthorized() {
99101
BearerTokenMetadata credentials =
100102
new BearerTokenMetadata("token");
101103
when(this.decoder.decode(any())).thenReturn(Mono.just(jwt()));
@@ -112,6 +114,26 @@ public void routeWhenAuthorized() {
112114
assertThat(hiRob).isEqualTo("Hi rob");
113115
}
114116

117+
@Test
118+
public void routeWhenAuthenticationBearerThenAuthorized() {
119+
MimeType authenticationMimeType = MimeTypeUtils.parseMimeType(MESSAGE_RSOCKET_AUTHENTICATION.getString());
120+
121+
BearerTokenMetadata credentials =
122+
new BearerTokenMetadata("token");
123+
when(this.decoder.decode(any())).thenReturn(Mono.just(jwt()));
124+
this.requester = requester()
125+
.setupMetadata(credentials, authenticationMimeType)
126+
.connectTcp(this.server.address().getHostName(), this.server.address().getPort())
127+
.block();
128+
129+
String hiRob = this.requester.route("secure.retrieve-mono")
130+
.data("rob")
131+
.retrieveMono(String.class)
132+
.block();
133+
134+
assertThat(hiRob).isEqualTo("Hi rob");
135+
}
136+
115137
private Jwt jwt() {
116138
return TestJwts.jwt()
117139
.claim(IdTokenClaimNames.ISS, "https://issuer.example.com")
@@ -145,7 +167,7 @@ public RSocketMessageHandler messageHandler() {
145167
@Bean
146168
public RSocketStrategies rsocketStrategies() {
147169
return RSocketStrategies.builder()
148-
.encoder(new BasicAuthenticationEncoder())
170+
.encoder(new BearerTokenAuthenticationEncoder())
149171
.build();
150172
}
151173

@@ -154,7 +176,7 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
154176
rsocket
155177
.authorizePayload(authorize ->
156178
authorize
157-
.route("secure.admin.*").authenticated()
179+
.anyRequest().authenticated()
158180
.anyExchange().permitAll()
159181
)
160182
.jwt(Customizer.withDefaults());

0 commit comments

Comments
 (0)