mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 09:12:14 +00:00
Add RSocket Authentication Extension Support
Fixes gh-7935
This commit is contained in:
parent
209c81d65d
commit
1d7208f8ef
@ -30,6 +30,7 @@ import org.springframework.security.config.Customizer;
|
|||||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
|
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
|
||||||
import org.springframework.security.rsocket.api.PayloadInterceptor;
|
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.core.PayloadSocketAcceptorInterceptor;
|
||||||
import org.springframework.security.rsocket.authentication.AnonymousPayloadInterceptor;
|
import org.springframework.security.rsocket.authentication.AnonymousPayloadInterceptor;
|
||||||
import org.springframework.security.rsocket.authentication.AuthenticationPayloadInterceptor;
|
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 reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,6 +118,8 @@ public class RSocketSecurity {
|
|||||||
|
|
||||||
private BasicAuthenticationSpec basicAuthSpec;
|
private BasicAuthenticationSpec basicAuthSpec;
|
||||||
|
|
||||||
|
private SimpleAuthenticationSpec simpleAuthSpec;
|
||||||
|
|
||||||
private JwtSpec jwtSpec;
|
private JwtSpec jwtSpec;
|
||||||
|
|
||||||
private AuthorizePayloadsSpec authorizePayload;
|
private AuthorizePayloadsSpec authorizePayload;
|
||||||
@ -145,6 +149,58 @@ public class RSocketSecurity {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds support for validating a username and password using
|
||||||
|
* <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Simple.md">Simple Authentication</a>
|
||||||
|
* @param simple a customizer
|
||||||
|
* @return RSocketSecurity for additional configuration
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public RSocketSecurity simpleAuthentication(Customizer<SimpleAuthenticationSpec> 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<BasicAuthenticationSpec> basic) {
|
public RSocketSecurity basicAuthentication(Customizer<BasicAuthenticationSpec> basic) {
|
||||||
if (this.basicAuthSpec == null) {
|
if (this.basicAuthSpec == null) {
|
||||||
this.basicAuthSpec = new BasicAuthenticationSpec();
|
this.basicAuthSpec = new BasicAuthenticationSpec();
|
||||||
@ -206,12 +262,17 @@ public class RSocketSecurity {
|
|||||||
return RSocketSecurity.this.authenticationManager;
|
return RSocketSecurity.this.authenticationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AuthenticationPayloadInterceptor build() {
|
protected List<AuthenticationPayloadInterceptor> build() {
|
||||||
ReactiveAuthenticationManager manager = getAuthenticationManager();
|
ReactiveAuthenticationManager manager = getAuthenticationManager();
|
||||||
AuthenticationPayloadInterceptor result = new AuthenticationPayloadInterceptor(manager);
|
AuthenticationPayloadInterceptor legacy = new AuthenticationPayloadInterceptor(manager);
|
||||||
result.setAuthenticationConverter(new BearerPayloadExchangeConverter());
|
legacy.setAuthenticationConverter(new BearerPayloadExchangeConverter());
|
||||||
result.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
|
legacy.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
|
||||||
return result;
|
|
||||||
|
AuthenticationPayloadInterceptor standard = new AuthenticationPayloadInterceptor(manager);
|
||||||
|
standard.setAuthenticationConverter(new AuthenticationPayloadExchangeConverter());
|
||||||
|
standard.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder());
|
||||||
|
|
||||||
|
return Arrays.asList(standard, legacy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JwtSpec() {}
|
private JwtSpec() {}
|
||||||
@ -240,8 +301,11 @@ public class RSocketSecurity {
|
|||||||
if (this.basicAuthSpec != null) {
|
if (this.basicAuthSpec != null) {
|
||||||
result.add(this.basicAuthSpec.build());
|
result.add(this.basicAuthSpec.build());
|
||||||
}
|
}
|
||||||
|
if (this.simpleAuthSpec != null) {
|
||||||
|
result.add(this.simpleAuthSpec.build());
|
||||||
|
}
|
||||||
if (this.jwtSpec != null) {
|
if (this.jwtSpec != null) {
|
||||||
result.add(this.jwtSpec.build());
|
result.addAll(this.jwtSpec.build());
|
||||||
}
|
}
|
||||||
result.add(anonymous());
|
result.add(anonymous());
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ class SecuritySocketAcceptorInterceptorConfiguration {
|
|||||||
}
|
}
|
||||||
rsocket
|
rsocket
|
||||||
.basicAuthentication(Customizer.withDefaults())
|
.basicAuthentication(Customizer.withDefaults())
|
||||||
|
.simpleAuthentication(Customizer.withDefaults())
|
||||||
.authorizePayload(authz ->
|
.authorizePayload(authz ->
|
||||||
authz
|
authz
|
||||||
.setup().authenticated()
|
.setup().authenticated()
|
||||||
|
@ -15,10 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.config.annotation.rsocket;
|
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.RSocketFactory;
|
||||||
import io.rsocket.frame.decoder.PayloadDecoder;
|
import io.rsocket.frame.decoder.PayloadDecoder;
|
||||||
import io.rsocket.transport.netty.server.CloseableChannel;
|
import io.rsocket.transport.netty.server.CloseableChannel;
|
||||||
@ -27,8 +23,6 @@ import org.junit.After;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
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.oauth2.jwt.TestJwts;
|
||||||
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
|
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
|
||||||
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
|
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.security.rsocket.metadata.BearerTokenMetadata;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
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.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@ -95,7 +97,7 @@ public class JwtITests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void routeWhenAuthorized() {
|
public void routeWhenBearerThenAuthorized() {
|
||||||
BearerTokenMetadata credentials =
|
BearerTokenMetadata credentials =
|
||||||
new BearerTokenMetadata("token");
|
new BearerTokenMetadata("token");
|
||||||
when(this.decoder.decode(any())).thenReturn(Mono.just(jwt()));
|
when(this.decoder.decode(any())).thenReturn(Mono.just(jwt()));
|
||||||
@ -112,6 +114,26 @@ public class JwtITests {
|
|||||||
assertThat(hiRob).isEqualTo("Hi rob");
|
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() {
|
private Jwt jwt() {
|
||||||
return TestJwts.jwt()
|
return TestJwts.jwt()
|
||||||
.claim(IdTokenClaimNames.ISS, "https://issuer.example.com")
|
.claim(IdTokenClaimNames.ISS, "https://issuer.example.com")
|
||||||
@ -145,7 +167,7 @@ public class JwtITests {
|
|||||||
@Bean
|
@Bean
|
||||||
public RSocketStrategies rsocketStrategies() {
|
public RSocketStrategies rsocketStrategies() {
|
||||||
return RSocketStrategies.builder()
|
return RSocketStrategies.builder()
|
||||||
.encoder(new BasicAuthenticationEncoder())
|
.encoder(new BearerTokenAuthenticationEncoder())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +176,7 @@ public class JwtITests {
|
|||||||
rsocket
|
rsocket
|
||||||
.authorizePayload(authorize ->
|
.authorizePayload(authorize ->
|
||||||
authorize
|
authorize
|
||||||
.route("secure.admin.*").authenticated()
|
.anyRequest().authenticated()
|
||||||
.anyExchange().permitAll()
|
.anyExchange().permitAll()
|
||||||
)
|
)
|
||||||
.jwt(Customizer.withDefaults());
|
.jwt(Customizer.withDefaults());
|
||||||
|
@ -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<String> payloads = new ArrayList<>();
|
||||||
|
|
||||||
|
@MessageMapping("**")
|
||||||
|
String retrieveMono(String payload) {
|
||||||
|
add(payload);
|
||||||
|
return "Hi " + payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(String p) {
|
||||||
|
this.payloads.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -32,7 +32,7 @@ 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.
|
This configuration enables <<rsocket-authentication-simple,simple authentication>> and sets up <<authorization,rsocket-authorization>> to require an authenticated user for any request.
|
||||||
|
|
||||||
== Adding SecuritySocketAcceptorInterceptor
|
== 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.
|
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.
|
This means that individual users can make requests but not make additional connections.
|
||||||
|
|
||||||
[[rsocket-authentication-basic]]
|
[[rsocket-authentication-simple]]
|
||||||
=== Basic Authentication
|
=== 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.
|
An explicit configuration can be found below.
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
@ -91,26 +97,28 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
|
|||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
.anyExchange().permitAll()
|
.anyExchange().permitAll()
|
||||||
)
|
)
|
||||||
.basicAuthentication(Customizer.withDefaults());
|
.simpleAuthentication(Customizer.withDefaults());
|
||||||
return rsocket.build();
|
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]
|
[source,java]
|
||||||
----
|
----
|
||||||
RSocketStrategies.Builder strategies = ...;
|
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:
|
It can then be used to send a username and password to the receiver in the setup:
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
|
MimeType authenticationMimeType =
|
||||||
|
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
|
||||||
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
|
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
|
||||||
Mono<RSocketRequester> requester = RSocketRequester.builder()
|
Mono<RSocketRequester> requester = RSocketRequester.builder()
|
||||||
.setupMetadata(credentials, UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE)
|
.setupMetadata(credentials, authenticationMimeType)
|
||||||
.rsocketStrategies(strategies.build())
|
.rsocketStrategies(strategies.build())
|
||||||
.connectTcp(host, port);
|
.connectTcp(host, port);
|
||||||
----
|
----
|
||||||
@ -125,7 +133,7 @@ UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "pas
|
|||||||
public Mono<AirportLocation> findRadar(String code) {
|
public Mono<AirportLocation> findRadar(String code) {
|
||||||
return this.requester.flatMap(req ->
|
return this.requester.flatMap(req ->
|
||||||
req.route("find.radar.{code}", code)
|
req.route("find.radar.{code}", code)
|
||||||
.metadata(credentials, UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE)
|
.metadata(credentials, authenticationMimeType)
|
||||||
.retrieveMono(AirportLocation.class)
|
.retrieveMono(AirportLocation.class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -134,7 +142,7 @@ public Mono<AirportLocation> findRadar(String code) {
|
|||||||
[[rsocket-authentication-jwt]]
|
[[rsocket-authentication-jwt]]
|
||||||
=== 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 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.
|
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]
|
[source,java]
|
||||||
----
|
----
|
||||||
String token = ...;
|
MimeType authenticationMimeType =
|
||||||
|
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
|
||||||
|
BearerTokenMetadata token = ...;
|
||||||
Mono<RSocketRequester> requester = RSocketRequester.builder()
|
Mono<RSocketRequester> requester = RSocketRequester.builder()
|
||||||
.setupMetadata(token, BearerTokenMetadata.BEARER_AUTHENTICATION_MIME_TYPE)
|
.setupMetadata(token, authenticationMimeType)
|
||||||
.connectTcp(host, port);
|
.connectTcp(host, port);
|
||||||
----
|
----
|
||||||
|
|
||||||
@ -182,13 +192,15 @@ Alternatively or additionally, the token can be sent in a request.
|
|||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
|
MimeType authenticationMimeType =
|
||||||
|
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
|
||||||
Mono<RSocketRequester> requester;
|
Mono<RSocketRequester> requester;
|
||||||
String token = ...;
|
BearerTokenMetadata token = ...;
|
||||||
|
|
||||||
public Mono<AirportLocation> findRadar(String code) {
|
public Mono<AirportLocation> findRadar(String code) {
|
||||||
return this.requester.flatMap(req ->
|
return this.requester.flatMap(req ->
|
||||||
req.route("find.radar.{code}", code)
|
req.route("find.radar.{code}", code)
|
||||||
.metadata(token, BearerTokenMetadata.BEARER_AUTHENTICATION_MIME_TYPE)
|
.metadata(token, authenticationMimeType)
|
||||||
.retrieveMono(AirportLocation.class)
|
.retrieveMono(AirportLocation.class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
* <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Authentication.md">Authentication Extension</a>.
|
||||||
|
* For
|
||||||
|
* <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Simple.md">Simple</a>
|
||||||
|
* a {@link UsernamePasswordAuthenticationToken} is returned. For
|
||||||
|
* <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Bearer.md">Bearer</a>
|
||||||
|
* 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<Authentication> 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<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,9 @@ import java.util.Map;
|
|||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
|
* @deprecated Basic Authentication did not evolve into a standard. Use Simple Authentication instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class BasicAuthenticationDecoder extends AbstractDecoder<UsernamePasswordMetadata> {
|
public class BasicAuthenticationDecoder extends AbstractDecoder<UsernamePasswordMetadata> {
|
||||||
public BasicAuthenticationDecoder() {
|
public BasicAuthenticationDecoder() {
|
||||||
super(UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE);
|
super(UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE);
|
||||||
|
@ -34,7 +34,9 @@ import java.util.Map;
|
|||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
|
* @deprecated Basic Authentication did not evolve into a standard. use {@link SimpleAuthenticationEncoder}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class BasicAuthenticationEncoder extends
|
public class BasicAuthenticationEncoder extends
|
||||||
AbstractEncoder<UsernamePasswordMetadata> {
|
AbstractEncoder<UsernamePasswordMetadata> {
|
||||||
|
|
||||||
|
@ -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 <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Bearer.md">Bearer Authentication</a>.
|
||||||
|
*
|
||||||
|
* @author Rob Winch
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public class BearerTokenAuthenticationEncoder extends
|
||||||
|
AbstractEncoder<BearerTokenMetadata> {
|
||||||
|
|
||||||
|
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<DataBuffer> encode(
|
||||||
|
Publisher<? extends BearerTokenMetadata> inputStream,
|
||||||
|
DataBufferFactory bufferFactory, ResolvableType elementType,
|
||||||
|
MimeType mimeType, Map<String, Object> 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<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,9 @@ public class BearerTokenMetadata {
|
|||||||
* Represents a bearer token which is encoded as a String.
|
* Represents a bearer token which is encoded as a String.
|
||||||
*
|
*
|
||||||
* See <a href="https://github.com/rsocket/rsocket/issues/272">rsocket/rsocket#272</a>
|
* See <a href="https://github.com/rsocket/rsocket/issues/272">rsocket/rsocket#272</a>
|
||||||
|
* @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");
|
public static final MimeType BEARER_AUTHENTICATION_MIME_TYPE = new MediaType("message", "x.rsocket.authentication.bearer.v0");
|
||||||
|
|
||||||
private final String token;
|
private final String token;
|
||||||
|
@ -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
|
||||||
|
* <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Simple.md">Simple</a>
|
||||||
|
* Authentication.
|
||||||
|
*
|
||||||
|
* @author Rob Winch
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public class SimpleAuthenticationEncoder extends
|
||||||
|
AbstractEncoder<UsernamePasswordMetadata> {
|
||||||
|
|
||||||
|
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<DataBuffer> encode(
|
||||||
|
Publisher<? extends UsernamePasswordMetadata> inputStream,
|
||||||
|
DataBufferFactory bufferFactory, ResolvableType elementType,
|
||||||
|
MimeType mimeType, Map<String, Object> 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<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -33,7 +33,9 @@ public final class UsernamePasswordMetadata {
|
|||||||
* {@code ${username-bytes-length}${username-bytes}${password-bytes}}.
|
* {@code ${username-bytes-length}${username-bytes}${password-bytes}}.
|
||||||
*
|
*
|
||||||
* See <a href="https://github.com/rsocket/rsocket/issues/272">rsocket/rsocket#272</a>
|
* See <a href="https://github.com/rsocket/rsocket/issues/272">rsocket/rsocket#272</a>
|
||||||
|
* @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");
|
public static final MimeType BASIC_AUTHENTICATION_MIME_TYPE = new MediaType("message", "x.rsocket.authentication.basic.v0");
|
||||||
|
|
||||||
private final String username;
|
private final String username;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||||
|
|
||||||
|
ext['rsocket.version'] = '1.0.0-RC6'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':spring-security-core')
|
compile project(':spring-security-core')
|
||||||
compile project(':spring-security-config')
|
compile project(':spring-security-config')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user