mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 09:12:14 +00:00
Add Default RSocketSecurity
Fixes gh-7361
This commit is contained in:
parent
5d0815bc76
commit
96d44cd4b7
@ -35,5 +35,5 @@ import java.lang.annotation.Target;
|
|||||||
@Documented
|
@Documented
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Import({ RSocketSecurityConfiguration.class })
|
@Import({ RSocketSecurityConfiguration.class, SecuritySocketAcceptorInterceptorConfiguration.class })
|
||||||
public @interface EnableRSocketSecurity { }
|
public @interface EnableRSocketSecurity { }
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
|
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
|
||||||
|
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
|
||||||
|
import org.springframework.security.rsocket.util.matcher.PayloadExchangeMatcher.MatchResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Rob Winch
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
class SecuritySocketAcceptorInterceptorConfiguration {
|
||||||
|
@Bean
|
||||||
|
SecuritySocketAcceptorInterceptor securitySocketAcceptorInterceptor(
|
||||||
|
ObjectProvider<PayloadSocketAcceptorInterceptor> rsocketInterceptor, ObjectProvider<RSocketSecurity> rsocketSecurity) {
|
||||||
|
PayloadSocketAcceptorInterceptor delegate = rsocketInterceptor
|
||||||
|
.getIfAvailable(() -> defaultInterceptor(rsocketSecurity));
|
||||||
|
return new SecuritySocketAcceptorInterceptor(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PayloadSocketAcceptorInterceptor defaultInterceptor(
|
||||||
|
ObjectProvider<RSocketSecurity> rsocketSecurity) {
|
||||||
|
RSocketSecurity rsocket = rsocketSecurity.getIfAvailable();
|
||||||
|
if (rsocket == null) {
|
||||||
|
throw new NoSuchBeanDefinitionException("No RSocketSecurity defined");
|
||||||
|
}
|
||||||
|
rsocket
|
||||||
|
.basicAuthentication(Customizer.withDefaults())
|
||||||
|
.authorizePayload(authz ->
|
||||||
|
authz
|
||||||
|
.setup().authenticated()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.matcher(e -> MatchResult.match()).permitAll()
|
||||||
|
);
|
||||||
|
return rsocket.build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.core.userdetails.MapReactiveUserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
|
||||||
|
import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
|
||||||
|
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 java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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 HelloRSocketITests {
|
||||||
|
@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()
|
||||||
|
)
|
||||||
|
.isNotNull();
|
||||||
|
// FIXME: https://github.com/rsocket/rsocket-java/issues/686
|
||||||
|
// .isInstanceOf(RejectedSetupException.class);
|
||||||
|
assertThat(this.controller.payloads).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void retrieveMonoWhenAuthorizedThenGranted() throws Exception {
|
||||||
|
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("rob", "password");
|
||||||
|
this.requester = RSocketRequester.builder()
|
||||||
|
.setupMetadata(credentials, UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE)
|
||||||
|
.rsocketStrategies(this.handler.getRSocketStrategies())
|
||||||
|
.connectTcp("localhost", this.server.address().getPort())
|
||||||
|
.block();
|
||||||
|
String data = "rob";
|
||||||
|
String hiRob = this.requester.route("secure.retrieve-mono")
|
||||||
|
.metadata(credentials, UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE)
|
||||||
|
.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 BasicAuthenticationEncoder())
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -35,6 +35,7 @@ import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
|||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
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.metadata.BasicAuthenticationEncoder;
|
import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
|
||||||
import org.springframework.security.rsocket.metadata.BearerTokenMetadata;
|
import org.springframework.security.rsocket.metadata.BearerTokenMetadata;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@ -64,7 +65,7 @@ public class JwtITests {
|
|||||||
RSocketMessageHandler handler;
|
RSocketMessageHandler handler;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
PayloadSocketAcceptorInterceptor interceptor;
|
SecuritySocketAcceptorInterceptor interceptor;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
ServerController controller;
|
ServerController controller;
|
||||||
|
@ -36,6 +36,7 @@ import org.springframework.security.core.userdetails.MapReactiveUserDetailsServi
|
|||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
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.metadata.BasicAuthenticationEncoder;
|
import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
|
||||||
import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
|
import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@ -58,7 +59,7 @@ public class RSocketMessageHandlerConnectionITests {
|
|||||||
RSocketMessageHandler handler;
|
RSocketMessageHandler handler;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
PayloadSocketAcceptorInterceptor interceptor;
|
SecuritySocketAcceptorInterceptor interceptor;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
ServerController controller;
|
ServerController controller;
|
||||||
|
@ -37,6 +37,7 @@ import org.springframework.security.core.userdetails.MapReactiveUserDetailsServi
|
|||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
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.metadata.BasicAuthenticationEncoder;
|
import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
|
||||||
import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
|
import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@ -47,6 +48,7 @@ import reactor.core.publisher.Mono;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
@ -61,7 +63,7 @@ public class RSocketMessageHandlerITests {
|
|||||||
RSocketMessageHandler handler;
|
RSocketMessageHandler handler;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
PayloadSocketAcceptorInterceptor interceptor;
|
SecuritySocketAcceptorInterceptor interceptor;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
ServerController controller;
|
ServerController controller;
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.core;
|
||||||
|
|
||||||
|
import io.rsocket.SocketAcceptor;
|
||||||
|
import io.rsocket.plugins.SocketAcceptorInterceptor;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SocketAcceptorInterceptor that applies Security through a delegate {@link SocketAcceptorInterceptor}. This allows
|
||||||
|
* security to be applied lazily to an application.
|
||||||
|
*
|
||||||
|
* @author Rob Winch
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
public class SecuritySocketAcceptorInterceptor implements SocketAcceptorInterceptor {
|
||||||
|
private final SocketAcceptorInterceptor acceptorInterceptor;
|
||||||
|
|
||||||
|
public SecuritySocketAcceptorInterceptor(SocketAcceptorInterceptor acceptorInterceptor) {
|
||||||
|
Assert.notNull(acceptorInterceptor, "acceptorInterceptor cannot be null");
|
||||||
|
this.acceptorInterceptor = acceptorInterceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAcceptor apply(SocketAcceptor socketAcceptor) {
|
||||||
|
return this.acceptorInterceptor.apply(socketAcceptor);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user