diff --git a/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurity.java
index a05e90edcc..8ab04005e8 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurity.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurity.java
@@ -30,6 +30,7 @@ import org.springframework.security.config.Customizer;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import org.springframework.security.rsocket.api.PayloadInterceptor;
+import org.springframework.security.rsocket.authentication.AuthenticationPayloadExchangeConverter;
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
import org.springframework.security.rsocket.authentication.AnonymousPayloadInterceptor;
import org.springframework.security.rsocket.authentication.AuthenticationPayloadInterceptor;
@@ -44,6 +45,7 @@ import org.springframework.security.rsocket.util.matcher.RoutePayloadExchangeMat
import reactor.core.publisher.Mono;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -116,6 +118,8 @@ public class RSocketSecurity {
private BasicAuthenticationSpec basicAuthSpec;
+ private SimpleAuthenticationSpec simpleAuthSpec;
+
private JwtSpec jwtSpec;
private AuthorizePayloadsSpec authorizePayload;
@@ -145,6 +149,58 @@ public class RSocketSecurity {
return this;
}
+ /**
+ * Adds support for validating a username and password using
+ * Simple Authentication
+ * @param simple a customizer
+ * @return RSocketSecurity for additional configuration
+ * @since 5.3
+ */
+ public RSocketSecurity simpleAuthentication(Customizer simple) {
+ if (this.simpleAuthSpec == null) {
+ this.simpleAuthSpec = new SimpleAuthenticationSpec();
+ }
+ simple.customize(this.simpleAuthSpec);
+ return this;
+ }
+
+ /**
+ * @since 5.3
+ */
+ public class SimpleAuthenticationSpec {
+ private ReactiveAuthenticationManager authenticationManager;
+
+ public SimpleAuthenticationSpec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
+ this.authenticationManager = authenticationManager;
+ return this;
+ }
+
+ private ReactiveAuthenticationManager getAuthenticationManager() {
+ if (this.authenticationManager == null) {
+ return RSocketSecurity.this.authenticationManager;
+ }
+ return this.authenticationManager;
+ }
+
+ protected AuthenticationPayloadInterceptor build() {
+ ReactiveAuthenticationManager manager = getAuthenticationManager();
+ AuthenticationPayloadInterceptor result = new AuthenticationPayloadInterceptor(manager);
+ result.setAuthenticationConverter(new AuthenticationPayloadExchangeConverter());
+ result.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
+ return result;
+ }
+
+ private SimpleAuthenticationSpec() {}
+ }
+
+ /**
+ * Adds authentication with BasicAuthenticationPayloadExchangeConverter.
+ *
+ * @param basic
+ * @return
+ * @deprecated Use {@link #simpleAuthentication(Customizer)}
+ */
+ @Deprecated
public RSocketSecurity basicAuthentication(Customizer basic) {
if (this.basicAuthSpec == null) {
this.basicAuthSpec = new BasicAuthenticationSpec();
@@ -206,12 +262,17 @@ public class RSocketSecurity {
return RSocketSecurity.this.authenticationManager;
}
- protected AuthenticationPayloadInterceptor build() {
+ protected List build() {
ReactiveAuthenticationManager manager = getAuthenticationManager();
- AuthenticationPayloadInterceptor result = new AuthenticationPayloadInterceptor(manager);
- result.setAuthenticationConverter(new BearerPayloadExchangeConverter());
- result.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
- return result;
+ AuthenticationPayloadInterceptor legacy = new AuthenticationPayloadInterceptor(manager);
+ legacy.setAuthenticationConverter(new BearerPayloadExchangeConverter());
+ legacy.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
+
+ AuthenticationPayloadInterceptor standard = new AuthenticationPayloadInterceptor(manager);
+ standard.setAuthenticationConverter(new AuthenticationPayloadExchangeConverter());
+ standard.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
+
+ return Arrays.asList(standard, legacy);
}
private JwtSpec() {}
@@ -240,8 +301,11 @@ public class RSocketSecurity {
if (this.basicAuthSpec != null) {
result.add(this.basicAuthSpec.build());
}
+ if (this.simpleAuthSpec != null) {
+ result.add(this.simpleAuthSpec.build());
+ }
if (this.jwtSpec != null) {
- result.add(this.jwtSpec.build());
+ result.addAll(this.jwtSpec.build());
}
result.add(anonymous());
diff --git a/config/src/main/java/org/springframework/security/config/annotation/rsocket/SecuritySocketAcceptorInterceptorConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/rsocket/SecuritySocketAcceptorInterceptorConfiguration.java
index a469b29939..cdd007d61e 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/rsocket/SecuritySocketAcceptorInterceptorConfiguration.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/rsocket/SecuritySocketAcceptorInterceptorConfiguration.java
@@ -47,6 +47,7 @@ class SecuritySocketAcceptorInterceptorConfiguration {
}
rsocket
.basicAuthentication(Customizer.withDefaults())
+ .simpleAuthentication(Customizer.withDefaults())
.authorizePayload(authz ->
authz
.setup().authenticated()
diff --git a/config/src/test/java/org/springframework/security/config/annotation/rsocket/JwtITests.java b/config/src/test/java/org/springframework/security/config/annotation/rsocket/JwtITests.java
index 23b2b8a53f..6b08f11ffd 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/rsocket/JwtITests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/rsocket/JwtITests.java
@@ -15,10 +15,6 @@
*/
package org.springframework.security.config.annotation.rsocket;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
import io.rsocket.RSocketFactory;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.transport.netty.server.CloseableChannel;
@@ -27,8 +23,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import reactor.core.publisher.Mono;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -43,12 +37,20 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.TestJwts;
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
-import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
+import org.springframework.security.rsocket.metadata.BearerTokenAuthenticationEncoder;
import org.springframework.security.rsocket.metadata.BearerTokenMetadata;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.util.MimeType;
+import org.springframework.util.MimeTypeUtils;
+import reactor.core.publisher.Mono;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static io.rsocket.metadata.WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
@@ -95,7 +97,7 @@ public class JwtITests {
}
@Test
- public void routeWhenAuthorized() {
+ public void routeWhenBearerThenAuthorized() {
BearerTokenMetadata credentials =
new BearerTokenMetadata("token");
when(this.decoder.decode(any())).thenReturn(Mono.just(jwt()));
@@ -112,6 +114,26 @@ public class JwtITests {
assertThat(hiRob).isEqualTo("Hi rob");
}
+ @Test
+ public void routeWhenAuthenticationBearerThenAuthorized() {
+ MimeType authenticationMimeType = MimeTypeUtils.parseMimeType(MESSAGE_RSOCKET_AUTHENTICATION.getString());
+
+ BearerTokenMetadata credentials =
+ new BearerTokenMetadata("token");
+ when(this.decoder.decode(any())).thenReturn(Mono.just(jwt()));
+ this.requester = requester()
+ .setupMetadata(credentials, authenticationMimeType)
+ .connectTcp(this.server.address().getHostName(), this.server.address().getPort())
+ .block();
+
+ String hiRob = this.requester.route("secure.retrieve-mono")
+ .data("rob")
+ .retrieveMono(String.class)
+ .block();
+
+ assertThat(hiRob).isEqualTo("Hi rob");
+ }
+
private Jwt jwt() {
return TestJwts.jwt()
.claim(IdTokenClaimNames.ISS, "https://issuer.example.com")
@@ -145,7 +167,7 @@ public class JwtITests {
@Bean
public RSocketStrategies rsocketStrategies() {
return RSocketStrategies.builder()
- .encoder(new BasicAuthenticationEncoder())
+ .encoder(new BearerTokenAuthenticationEncoder())
.build();
}
@@ -154,7 +176,7 @@ public class JwtITests {
rsocket
.authorizePayload(authorize ->
authorize
- .route("secure.admin.*").authenticated()
+ .anyRequest().authenticated()
.anyExchange().permitAll()
)
.jwt(Customizer.withDefaults());
diff --git a/config/src/test/java/org/springframework/security/config/annotation/rsocket/SimpleAuthenticationITests.java b/config/src/test/java/org/springframework/security/config/annotation/rsocket/SimpleAuthenticationITests.java
new file mode 100644
index 0000000000..9c9f4607c3
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/annotation/rsocket/SimpleAuthenticationITests.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 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 org.springframework.security.config.annotation.rsocket;
+
+import io.rsocket.RSocketFactory;
+import io.rsocket.exceptions.ApplicationErrorException;
+import io.rsocket.frame.decoder.PayloadDecoder;
+import io.rsocket.transport.netty.server.CloseableChannel;
+import io.rsocket.transport.netty.server.TcpServerTransport;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+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.RSocketStrategies;
+import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
+import org.springframework.security.config.Customizer;
+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.PayloadSocketAcceptorInterceptor;
+import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
+import org.springframework.security.rsocket.metadata.SimpleAuthenticationEncoder;
+import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
+import org.springframework.stereotype.Controller;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.util.MimeType;
+import org.springframework.util.MimeTypeUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static io.rsocket.metadata.WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+/**
+ * @author Rob Winch
+ */
+@ContextConfiguration
+@RunWith(SpringRunner.class)
+public class SimpleAuthenticationITests {
+ @Autowired
+ RSocketMessageHandler handler;
+
+ @Autowired
+ SecuritySocketAcceptorInterceptor interceptor;
+
+ @Autowired
+ ServerController controller;
+
+ private CloseableChannel server;
+
+ private RSocketRequester requester;
+
+ @Before
+ public void setup() {
+ this.server = RSocketFactory.receive()
+ .frameDecoder(PayloadDecoder.ZERO_COPY)
+ .addSocketAcceptorPlugin(this.interceptor)
+ .acceptor(this.handler.responder())
+ .transport(TcpServerTransport.create("localhost", 0))
+ .start()
+ .block();
+ }
+
+ @After
+ public void dispose() {
+ this.requester.rsocket().dispose();
+ this.server.dispose();
+ this.controller.payloads.clear();
+ }
+
+ @Test
+ public void retrieveMonoWhenSecureThenDenied() throws Exception {
+ this.requester = RSocketRequester.builder()
+ .rsocketStrategies(this.handler.getRSocketStrategies())
+ .connectTcp("localhost", this.server.address().getPort())
+ .block();
+
+ String data = "rob";
+ assertThatCode(() -> this.requester.route("secure.retrieve-mono")
+ .data(data)
+ .retrieveMono(String.class)
+ .block()
+ )
+ .isInstanceOf(ApplicationErrorException.class);
+ assertThat(this.controller.payloads).isEmpty();
+ }
+
+ @Test
+ public void retrieveMonoWhenAuthorizedThenGranted() {
+ MimeType authenticationMimeType = MimeTypeUtils.parseMimeType(MESSAGE_RSOCKET_AUTHENTICATION.getString());
+
+ UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("rob", "password");
+ this.requester = RSocketRequester.builder()
+ .setupMetadata(credentials, authenticationMimeType)
+ .rsocketStrategies(this.handler.getRSocketStrategies())
+ .connectTcp("localhost", this.server.address().getPort())
+ .block();
+ String data = "rob";
+ String hiRob = this.requester.route("secure.retrieve-mono")
+ .metadata(credentials, authenticationMimeType)
+ .data(data)
+ .retrieveMono(String.class)
+ .block();
+
+ assertThat(hiRob).isEqualTo("Hi rob");
+ assertThat(this.controller.payloads).containsOnly(data);
+ }
+
+ @Configuration
+ @EnableRSocketSecurity
+ static class Config {
+
+ @Bean
+ public ServerController controller() {
+ return new ServerController();
+ }
+
+ @Bean
+ public RSocketMessageHandler messageHandler() {
+ RSocketMessageHandler handler = new RSocketMessageHandler();
+ handler.setRSocketStrategies(rsocketStrategies());
+ return handler;
+ }
+
+ @Bean
+ public RSocketStrategies rsocketStrategies() {
+ return RSocketStrategies.builder()
+ .encoder(new SimpleAuthenticationEncoder())
+ .build();
+ }
+
+ @Bean
+ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
+ rsocket
+ .authorizePayload(authorize ->
+ authorize
+ .anyRequest().authenticated()
+ .anyExchange().permitAll()
+ )
+ .simpleAuthentication(Customizer.withDefaults());
+ return rsocket.build();
+ }
+
+ @Bean
+ MapReactiveUserDetailsService uds() {
+ UserDetails rob = User.withDefaultPasswordEncoder()
+ .username("rob")
+ .password("password")
+ .roles("USER", "ADMIN")
+ .build();
+ return new MapReactiveUserDetailsService(rob);
+ }
+ }
+
+ @Controller
+ static class ServerController {
+ private List payloads = new ArrayList<>();
+
+ @MessageMapping("**")
+ String retrieveMono(String payload) {
+ add(payload);
+ return "Hi " + payload;
+ }
+
+ private void add(String p) {
+ this.payloads.add(p);
+ }
+ }
+
+}
diff --git a/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc b/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc
index 0bbf66f735..0b942f6198 100644
--- a/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc
@@ -32,7 +32,7 @@ public class HelloRSocketSecurityConfig {
}
-----
-This configuration enables <> and sets up <> to require an authenticated user for any request.
+This configuration enables <> and sets up <> to require an authenticated user for any request.
== Adding SecuritySocketAcceptorInterceptor
@@ -73,12 +73,18 @@ If we need to restrict the connection to the web application itself, we can prov
Then each user would have different authorities but not the `SETUP` authority.
This means that individual users can make requests but not make additional connections.
-[[rsocket-authentication-basic]]
-=== Basic Authentication
+[[rsocket-authentication-simple]]
+=== Simple Authentication
-Spring Security has early support for https://github.com/rsocket/rsocket/issues/272[RSocket's Basic Authentication Metadata Extension].
+Spring Security has support for https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Simple.md[Simple Authentication Metadata Extension].
-The RSocket receiver can decode the credentials using `BasicAuthenticationPayloadExchangeConverter` which is automatically setup using the `basicAuthentication` portion of the DSL.
+[NOTE]
+====
+Basic Authentication drafts evolved into Simple Authentication and is only supported for backward compatibility.
+See `RSocketSecurity.basicAuthentication(Customizer)` for setting it up.
+====
+
+The RSocket receiver can decode the credentials using `AuthenticationPayloadExchangeConverter` which is automatically setup using the `simpleAuthentication` portion of the DSL.
An explicit configuration can be found below.
[source,java]
@@ -91,26 +97,28 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
.anyRequest().authenticated()
.anyExchange().permitAll()
)
- .basicAuthentication(Customizer.withDefaults());
+ .simpleAuthentication(Customizer.withDefaults());
return rsocket.build();
}
----
-The RSocket sender can send credentials using `BasicAuthenticationEncoder` which can be added to Spring's `RSocketStrategies`.
+The RSocket sender can send credentials using `SimpleAuthenticationEncoder` which can be added to Spring's `RSocketStrategies`.
[source,java]
----
RSocketStrategies.Builder strategies = ...;
-strategies.encoder(new BasicAuthenticationEncoder());
+strategies.encoder(new SimpleAuthenticationEncoder());
----
It can then be used to send a username and password to the receiver in the setup:
[source,java]
----
+MimeType authenticationMimeType =
+ MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
Mono requester = RSocketRequester.builder()
- .setupMetadata(credentials, UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE)
+ .setupMetadata(credentials, authenticationMimeType)
.rsocketStrategies(strategies.build())
.connectTcp(host, port);
----
@@ -125,7 +133,7 @@ UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "pas
public Mono findRadar(String code) {
return this.requester.flatMap(req ->
req.route("find.radar.{code}", code)
- .metadata(credentials, UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE)
+ .metadata(credentials, authenticationMimeType)
.retrieveMono(AirportLocation.class)
);
}
@@ -134,7 +142,7 @@ public Mono findRadar(String code) {
[[rsocket-authentication-jwt]]
=== JWT
-Spring Security has early support for https://github.com/rsocket/rsocket/issues/272[RSocket's Bearer Token Authentication Metadata Extension].
+Spring Security has support for https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Bearer.md[Bearer Token Authentication Metadata Extension].
The support comes in the form of authenticating a JWT (determining the JWT is valid) and then using the JWT to make authorization decisions.
The RSocket receiver can decode the credentials using `BearerPayloadExchangeConverter` which is automatically setup using the `jwt` portion of the DSL.
@@ -172,9 +180,11 @@ For example, the token can be sent at setup time:
[source,java]
----
-String token = ...;
+MimeType authenticationMimeType =
+ MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
+BearerTokenMetadata token = ...;
Mono requester = RSocketRequester.builder()
- .setupMetadata(token, BearerTokenMetadata.BEARER_AUTHENTICATION_MIME_TYPE)
+ .setupMetadata(token, authenticationMimeType)
.connectTcp(host, port);
----
@@ -182,13 +192,15 @@ Alternatively or additionally, the token can be sent in a request.
[source,java]
----
+MimeType authenticationMimeType =
+ MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
Mono requester;
-String token = ...;
+BearerTokenMetadata token = ...;
public Mono findRadar(String code) {
return this.requester.flatMap(req ->
req.route("find.radar.{code}", code)
- .metadata(token, BearerTokenMetadata.BEARER_AUTHENTICATION_MIME_TYPE)
+ .metadata(token, authenticationMimeType)
.retrieveMono(AirportLocation.class)
);
}
diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadExchangeConverter.java b/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadExchangeConverter.java
new file mode 100644
index 0000000000..8c38b1e315
--- /dev/null
+++ b/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadExchangeConverter.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 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 org.springframework.security.rsocket.authentication;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.rsocket.metadata.WellKnownMimeType;
+import io.rsocket.metadata.security.AuthMetadataFlyweight;
+import io.rsocket.metadata.security.WellKnownAuthType;
+import org.springframework.core.codec.ByteArrayDecoder;
+import org.springframework.messaging.rsocket.DefaultMetadataExtractor;
+import org.springframework.messaging.rsocket.MetadataExtractor;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
+import org.springframework.security.rsocket.api.PayloadExchange;
+import org.springframework.util.MimeType;
+import org.springframework.util.MimeTypeUtils;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+/**
+ * Converts from the {@link PayloadExchange} for
+ * Authentication Extension.
+ * For
+ * Simple
+ * a {@link UsernamePasswordAuthenticationToken} is returned. For
+ * Bearer
+ * a {@link BearerTokenAuthenticationToken} is returned.
+ *
+ * @author Rob Winch
+ * @since 5.3
+ */
+public class AuthenticationPayloadExchangeConverter implements PayloadExchangeAuthenticationConverter {
+ private static final MimeType COMPOSITE_METADATA_MIME_TYPE = MimeTypeUtils.parseMimeType(
+ WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString());
+
+ private static final MimeType AUTHENTICATION_MIME_TYPE = MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
+
+ private MetadataExtractor metadataExtractor = createDefaultExtractor();
+
+ @Override
+ public Mono convert(PayloadExchange exchange) {
+ return Mono.fromCallable(() -> this.metadataExtractor
+ .extract(exchange.getPayload(), this.COMPOSITE_METADATA_MIME_TYPE))
+ .flatMap(metadata -> Mono.justOrEmpty(authentication(metadata)));
+ }
+
+ private Authentication authentication(Map metadata) {
+ byte[] authenticationMetadata = (byte[]) metadata.get("authentication");
+ if (authenticationMetadata == null) {
+ return null;
+ }
+ ByteBuf rawAuthentication = ByteBufAllocator.DEFAULT.buffer().writeBytes(authenticationMetadata);
+ if (!AuthMetadataFlyweight.isWellKnownAuthType(rawAuthentication)) {
+ return null;
+ }
+ WellKnownAuthType wellKnownAuthType = AuthMetadataFlyweight.decodeWellKnownAuthType(rawAuthentication);
+ if (WellKnownAuthType.SIMPLE.equals(wellKnownAuthType)) {
+ return simple(rawAuthentication);
+ } else if (WellKnownAuthType.BEARER.equals(wellKnownAuthType)) {
+ return bearer(rawAuthentication);
+ }
+ throw new IllegalArgumentException("Unknown Mime Type " + wellKnownAuthType);
+ }
+
+ private Authentication simple(ByteBuf rawAuthentication) {
+ ByteBuf rawUsername = AuthMetadataFlyweight.decodeUsername(rawAuthentication);
+ String username = rawUsername.toString(StandardCharsets.UTF_8);
+ ByteBuf rawPassword = AuthMetadataFlyweight.decodePassword(rawAuthentication);
+ String password = rawPassword.toString(StandardCharsets.UTF_8);
+ return new UsernamePasswordAuthenticationToken(username, password);
+ }
+
+ private Authentication bearer(ByteBuf rawAuthentication) {
+ char[] rawToken = AuthMetadataFlyweight.decodeBearerTokenAsCharArray(rawAuthentication);
+ String token = new String(rawToken);
+ return new BearerTokenAuthenticationToken(token);
+ }
+
+ private static MetadataExtractor createDefaultExtractor() {
+ DefaultMetadataExtractor result = new DefaultMetadataExtractor(new ByteArrayDecoder());
+ result.metadataToExtract(AUTHENTICATION_MIME_TYPE, byte[].class, "authentication");
+ return result;
+ }
+}
diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationDecoder.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationDecoder.java
index 5085e5a833..6b8a45fe41 100644
--- a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationDecoder.java
+++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationDecoder.java
@@ -31,7 +31,9 @@ import java.util.Map;
*
* @author Rob Winch
* @since 5.2
+ * @deprecated Basic Authentication did not evolve into a standard. Use Simple Authentication instead.
*/
+@Deprecated
public class BasicAuthenticationDecoder extends AbstractDecoder {
public BasicAuthenticationDecoder() {
super(UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE);
diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationEncoder.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationEncoder.java
index 9d088f5a2a..75e3f909ac 100644
--- a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationEncoder.java
+++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationEncoder.java
@@ -34,7 +34,9 @@ import java.util.Map;
*
* @author Rob Winch
* @since 5.2
+ * @deprecated Basic Authentication did not evolve into a standard. use {@link SimpleAuthenticationEncoder}
*/
+@Deprecated
public class BasicAuthenticationEncoder extends
AbstractEncoder {
diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenAuthenticationEncoder.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenAuthenticationEncoder.java
new file mode 100644
index 0000000000..3a513c4dd5
--- /dev/null
+++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenAuthenticationEncoder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 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 org.springframework.security.rsocket.metadata;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.rsocket.metadata.security.AuthMetadataFlyweight;
+import org.reactivestreams.Publisher;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.codec.AbstractEncoder;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferFactory;
+import org.springframework.core.io.buffer.NettyDataBufferFactory;
+import org.springframework.util.MimeType;
+import org.springframework.util.MimeTypeUtils;
+import reactor.core.publisher.Flux;
+
+import java.util.Map;
+
+/**
+ * Encodes Bearer Authentication.
+ *
+ * @author Rob Winch
+ * @since 5.3
+ */
+public class BearerTokenAuthenticationEncoder extends
+ AbstractEncoder {
+
+ private static final MimeType AUTHENTICATION_MIME_TYPE = MimeTypeUtils.parseMimeType("message/x.rsocket.authentication.v0");
+
+ private NettyDataBufferFactory defaultBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
+
+ public BearerTokenAuthenticationEncoder() {
+ super(AUTHENTICATION_MIME_TYPE);
+ }
+
+ @Override
+ public Flux encode(
+ Publisher extends BearerTokenMetadata> inputStream,
+ DataBufferFactory bufferFactory, ResolvableType elementType,
+ MimeType mimeType, Map hints) {
+ return Flux.from(inputStream).map(credentials ->
+ encodeValue(credentials, bufferFactory, elementType, mimeType, hints));
+ }
+
+ @Override
+ public DataBuffer encodeValue(BearerTokenMetadata credentials,
+ DataBufferFactory bufferFactory, ResolvableType valueType, MimeType mimeType,
+ Map hints) {
+ String token = credentials.getToken();
+ NettyDataBufferFactory factory = nettyFactory(bufferFactory);
+ ByteBufAllocator allocator = factory.getByteBufAllocator();
+ ByteBuf simpleAuthentication = AuthMetadataFlyweight
+ .encodeBearerMetadata(allocator, token.toCharArray());
+ return factory.wrap(simpleAuthentication);
+ }
+
+ private NettyDataBufferFactory nettyFactory(DataBufferFactory bufferFactory) {
+ if (bufferFactory instanceof NettyDataBufferFactory) {
+ return (NettyDataBufferFactory) bufferFactory;
+ }
+ return this.defaultBufferFactory;
+ }
+}
diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenMetadata.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenMetadata.java
index e252fa21f3..5998b07cdd 100644
--- a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenMetadata.java
+++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenMetadata.java
@@ -32,7 +32,9 @@ public class BearerTokenMetadata {
* Represents a bearer token which is encoded as a String.
*
* See rsocket/rsocket#272
+ * @deprecated Basic did not evolve into the standard. Instead use Simple Authentication MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString())
*/
+ @Deprecated
public static final MimeType BEARER_AUTHENTICATION_MIME_TYPE = new MediaType("message", "x.rsocket.authentication.bearer.v0");
private final String token;
diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/SimpleAuthenticationEncoder.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/SimpleAuthenticationEncoder.java
new file mode 100644
index 0000000000..64e4dc2e2b
--- /dev/null
+++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/SimpleAuthenticationEncoder.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 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 org.springframework.security.rsocket.metadata;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.rsocket.metadata.security.AuthMetadataFlyweight;
+import org.reactivestreams.Publisher;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.codec.AbstractEncoder;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferFactory;
+import org.springframework.core.io.buffer.NettyDataBufferFactory;
+import org.springframework.util.MimeType;
+import org.springframework.util.MimeTypeUtils;
+import reactor.core.publisher.Flux;
+
+import java.util.Map;
+
+/**
+ * Encodes
+ * Simple
+ * Authentication.
+ *
+ * @author Rob Winch
+ * @since 5.3
+ */
+public class SimpleAuthenticationEncoder extends
+ AbstractEncoder {
+
+ private static final MimeType AUTHENTICATION_MIME_TYPE = MimeTypeUtils.parseMimeType("message/x.rsocket.authentication.v0");
+
+ private NettyDataBufferFactory defaultBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
+
+ public SimpleAuthenticationEncoder() {
+ super(AUTHENTICATION_MIME_TYPE);
+ }
+
+ @Override
+ public Flux encode(
+ Publisher extends UsernamePasswordMetadata> inputStream,
+ DataBufferFactory bufferFactory, ResolvableType elementType,
+ MimeType mimeType, Map hints) {
+ return Flux.from(inputStream).map(credentials ->
+ encodeValue(credentials, bufferFactory, elementType, mimeType, hints));
+ }
+
+ @Override
+ public DataBuffer encodeValue(UsernamePasswordMetadata credentials,
+ DataBufferFactory bufferFactory, ResolvableType valueType, MimeType mimeType,
+ Map hints) {
+ String username = credentials.getUsername();
+ String password = credentials.getPassword();
+ NettyDataBufferFactory factory = nettyFactory(bufferFactory);
+ ByteBufAllocator allocator = factory.getByteBufAllocator();
+ ByteBuf simpleAuthentication = AuthMetadataFlyweight
+ .encodeSimpleMetadata(allocator, username.toCharArray(), password.toCharArray());
+ return factory.wrap(simpleAuthentication);
+ }
+
+ private NettyDataBufferFactory nettyFactory(DataBufferFactory bufferFactory) {
+ if (bufferFactory instanceof NettyDataBufferFactory) {
+ return (NettyDataBufferFactory) bufferFactory;
+ }
+ return this.defaultBufferFactory;
+ }
+}
diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/UsernamePasswordMetadata.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/UsernamePasswordMetadata.java
index e99e23aa40..dab4ad6ea5 100644
--- a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/UsernamePasswordMetadata.java
+++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/UsernamePasswordMetadata.java
@@ -33,7 +33,9 @@ public final class UsernamePasswordMetadata {
* {@code ${username-bytes-length}${username-bytes}${password-bytes}}.
*
* See rsocket/rsocket#272
+ * @deprecated Basic did not evolve into the standard. Instead use Simple Authentication MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString())
*/
+ @Deprecated
public static final MimeType BASIC_AUTHENTICATION_MIME_TYPE = new MediaType("message", "x.rsocket.authentication.basic.v0");
private final String username;
diff --git a/samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle b/samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle
index f2b788a0e2..b2fdafe9fd 100644
--- a/samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle
+++ b/samples/boot/hellorsocket/spring-security-samples-boot-hellorsocket.gradle
@@ -1,5 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
+ext['rsocket.version'] = '1.0.0-RC6'
+
dependencies {
compile project(':spring-security-core')
compile project(':spring-security-config')