浏览代码

Add RSocket Reference

Fixes gh-7502
Rob Winch 5 年之前
父节点
当前提交
d83aa34dde

+ 1 - 1
docs/manual/src/docs/asciidoc/_includes/about/whats-new.adoc

@@ -44,7 +44,7 @@ Below are the highlights of the release.
 
 === Core
 
-* Introducing https://github.com/spring-projects/spring-security/issues/7360[RSocket] support
+* Introducing <<rsoket,RSocket>> support
 * Introducing https://github.com/spring-projects/spring-security/issues/6019[SAML Service Provider] support
 * Introducing https://github.com/spring-projects/spring-security/issues/6722[AuthenticationManagerResolver]
 * Introducing https://github.com/spring-projects/spring-security/issues/6506[AuthenticationFilter]

+ 2 - 0
docs/manual/src/docs/asciidoc/_includes/reactive/index.adoc

@@ -17,3 +17,5 @@ include::webclient.adoc[leveloffset=+1]
 include::method.adoc[leveloffset=+1]
 
 include::test.adoc[leveloffset=+1]
+
+include::rsocket.adoc[leveloffset=+1]

+ 211 - 0
docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc

@@ -0,0 +1,211 @@
+[[rsocket]]
+= RSocket Security
+
+Spring Security's RSocket support relies on a `SocketAcceptorInterceptor`.
+The main entry point into security is found in the `PayloadSocketAcceptorInterceptor` which adapts the RSocket APIs to allow intercepting a `PayloadExchange` with `PayloadInterceptor` implementations.
+
+== Minimal RSocket Security Configuration
+
+You can find a minimal RSocket Security configuration below:
+
+[source,java]
+-----
+@Configuration
+@EnableRSocketSecurity
+public class HelloRSocketSecurityConfig {
+
+	@Bean
+	public MapReactiveUserDetailsService userDetailsService() {
+		UserDetails user = User.withDefaultPasswordEncoder()
+			.username("user")
+			.password("user")
+			.roles("USER")
+			.build();
+		return new MapReactiveUserDetailsService(user);
+	}
+}
+-----
+
+This configuration enables <<rsocket-authentication-basic,basic authentication>> and sets up <<authorization,rsocket-authorization>> to require an authenticated user for any request.
+
+[[rsocket-authentication]]
+== RSocket Authentication
+
+RSocket authentication is performed with `AuthenticationPayloadInterceptor` which acts as a controller to invoke a `ReactiveAuthenticationManager` instance.
+
+[[rsocket-authentication-setup-vs-request]]
+=== Authentication at Setup vs Request Time
+
+Generally, authentication can occur at setup time and/or request time.
+
+Authentication at setup time makes sense in a few scenarios.
+A common scenarios is when a single user (i.e. mobile connection) is leveraging an RSocket connection.
+In this case only a single user is leveraging the connection, so authentication can be done once at connection time.
+
+In a scenario where the RSocket connection is shared it makes sense to send credentials on each request.
+For example, a web application that connects to an RSocket server as a downstream service would make a single connection that all users leverage.
+In this case, if the RSocket server needs to perform authorization based on the web application's users credentials per request makes sense.
+
+In some scenarios authentication at setup and per request makes sense.
+Consider a web application as described previously.
+If we need to restrict the connection to the web application itself, we can provide a credential with a `SETUP` authority at connection time.
+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
+
+Spring Security has early support for https://github.com/rsocket/rsocket/issues/272[RSocket's Basic Authentication Metadata Extension].
+
+The RSocket receiver can decode the credentials using `BasicAuthenticationPayloadExchangeConverter` which is automatically setup using the `basicAuthentication` portion of the DSL.
+An explicit configuration can be found below.
+
+[source,java]
+----
+@Bean
+PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
+	rsocket
+		.authorizePayload(authorize ->
+			authorize
+					.anyRequest().authenticated()
+					.anyExchange().permitAll()
+		)
+		.basicAuthentication(Customizer.withDefaults());
+	return rsocket.build();
+}
+----
+
+The RSocket sender can send credentials using `BasicAuthenticationEncoder` which can be added to Spring's `RSocketStrategies`.
+
+[source,java]
+----
+RSocketStrategies.Builder strategies = ...;
+strategies.encoder(new BasicAuthenticationEncoder());
+----
+
+It can then be used to send a username and password to the receiver in the setup:
+
+[source,java]
+----
+UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
+Mono<RSocketRequester> requester = RSocketRequester.builder()
+	.setupMetadata(credentials, UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE)
+	.rsocketStrategies(strategies.build())
+	.connectTcp(host, port);
+----
+
+Alternatively or additionally, a username and password can be sent in a request.
+
+[source,java]
+----
+Mono<RSocketRequester> requester;
+UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
+
+public Mono<AirportLocation> findRadar(String code) {
+	return this.requester.flatMap(req ->
+		req.route("find.radar.{code}", code)
+			.metadata(credentials, UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE)
+			.retrieveMono(AirportLocation.class)
+	);
+}
+----
+
+[[rsocket-authentication-jwt]]
+=== JWT
+
+Spring Security has early support for https://github.com/rsocket/rsocket/issues/272[RSocket's 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.
+An example configuration can be found below:
+
+[source,java]
+----
+@Bean
+PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
+	rsocket
+		.authorizePayload(authorize ->
+			authorize
+				.anyRequest().authenticated()
+				.anyExchange().permitAll()
+		)
+		.jwt(Customizer.withDefaults());
+	return rsocket.build();
+}
+----
+
+The configuration above relies on the existence of a `ReactiveJwtDecoder` `@Bean` being present.
+An example of creating one from the issuer can be found below:
+
+[source,java]
+----
+@Bean
+ReactiveJwtDecoder jwtDecoder() {
+	return ReactiveJwtDecoders
+		.fromIssuerLocation("https://example.com/auth/realms/demo");
+}
+----
+
+The RSocket sender does not need to do anything special to send the token because the value is just a simple String.
+For example, the token can be sent at setup time:
+
+[source,java]
+----
+String token = ...;
+Mono<RSocketRequester> requester = RSocketRequester.builder()
+	.setupMetadata(token, BearerTokenMetadata.BEARER_AUTHENTICATION_MIME_TYPE)
+	.connectTcp(host, port);
+----
+
+Alternatively or additionally, the token can be sent in a request.
+
+[source,java]
+----
+Mono<RSocketRequester> requester;
+String token = ...;
+
+public Mono<AirportLocation> findRadar(String code) {
+	return this.requester.flatMap(req ->
+		req.route("find.radar.{code}", code)
+	        .metadata(token, BearerTokenMetadata.BEARER_AUTHENTICATION_MIME_TYPE)
+			.retrieveMono(AirportLocation.class)
+	);
+}
+----
+
+[[rsocket-authorization]]
+== RSocket Authorization
+
+RSocket authorization is performed with `AuthorizationPayloadInterceptor` which acts as a controller to invoke a `ReactiveAuthorizationManager` instance.
+The DSL can be used to setup authorization rules based upon the `PayloadExchange`.
+An example configuration can be found below:
+
+[[source,java]]
+----
+rsocket
+	.authorizePayload(authorize ->
+		authz
+			.setup().hasRole("SETUP") // <1>
+			.route("fetch.profile.me").authenticated() // <2>
+			.matcher(payloadExchange -> isMatch(payloadExchange)) // <3>
+				.hasRole("CUSTOM")
+			.route("fetch.profile.{username}") // <4>
+				.access((authentication, context) -> checkFriends(authentication, context))
+			.anyRequest().authenticated() // <5>
+			.anyExchange().permitAll() // <6>
+	)
+----
+<1> Setting up a connection requires the authority `ROLE_SETUP`
+<2> If the route is `fetch.profile.me` authorization only requires the user be authenticated
+<3> In this rule we setup a custom matcher where authorization requires the user to have the authority `ROLE_CUSTOM`
+<4> This rule leverages custom authorization.
+The matcher expresses a variable with the name `username` that is made available in the `context`.
+A custom authorization rule is exposed in the `checkFriends` method.
+<5> This rule ensures that request that does not already have a rule will require the user to be authenticated.
+A request is where the metadata is included.
+It would not include additional payloads.
+<6> This rule ensures that any exchange that does not already have a rule is allowed for anyone.
+In this example, it means that payloads that have no metadata have no authorization rules.
+
+It is important to understand that authorization rules are performed in order.
+Only the first authorization rule that matches will be invoked.