| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 | 
							- [[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.
 
- You can find a few sample applications that demonstrate the code below:
 
- * Hello RSocket {gh-samples-url}/reactive/rsocket/hello-security[hellorsocket]
 
- * https://github.com/rwinch/spring-flights/tree/security[Spring Flights]
 
- == Minimal RSocket Security Configuration
 
- You can find a minimal RSocket Security configuration below:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- -----
 
- @Configuration
 
- @EnableRSocketSecurity
 
- public class HelloRSocketSecurityConfig {
 
- 	@Bean
 
- 	public MapReactiveUserDetailsService userDetailsService() {
 
- 		UserDetails user = User.withDefaultPasswordEncoder()
 
- 			.username("user")
 
- 			.password("user")
 
- 			.roles("USER")
 
- 			.build();
 
- 		return new MapReactiveUserDetailsService(user);
 
- 	}
 
- }
 
- -----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Configuration
 
- @EnableRSocketSecurity
 
- open class HelloRSocketSecurityConfig {
 
-     @Bean
 
-     open fun userDetailsService(): MapReactiveUserDetailsService {
 
-         val user = User.withDefaultPasswordEncoder()
 
-             .username("user")
 
-             .password("user")
 
-             .roles("USER")
 
-             .build()
 
-         return MapReactiveUserDetailsService(user)
 
-     }
 
- }
 
- ----
 
- ======
 
- This configuration enables <<rsocket-authentication-simple,simple authentication>> and sets up <<rsocket-authorization,rsocket-authorization>> to require an authenticated user for any request.
 
- == Adding SecuritySocketAcceptorInterceptor
 
- For Spring Security to work we need to apply `SecuritySocketAcceptorInterceptor` to the `ServerRSocketFactory`.
 
- This is what connects our `PayloadSocketAcceptorInterceptor` we created with the RSocket infrastructure.
 
- In a Spring Boot application this is done automatically using `RSocketSecurityAutoConfiguration` with the following code.
 
- [source,java]
 
- ----
 
- @Bean
 
- RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInterceptor interceptor) {
 
-     return (server) -> server.interceptors((registry) -> registry.forSocketAcceptor(interceptor));
 
- }
 
- ----
 
- [[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-simple]]
 
- === Simple Authentication
 
- Spring Security has support for https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Simple.md[Simple Authentication Metadata Extension].
 
- [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.
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
 
- 	rsocket
 
- 		.authorizePayload(authorize ->
 
- 			authorize
 
- 					.anyRequest().authenticated()
 
- 					.anyExchange().permitAll()
 
- 		)
 
- 		.simpleAuthentication(Customizer.withDefaults());
 
- 	return rsocket.build();
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- open fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
 
-     rsocket
 
-         .authorizePayload { authorize -> authorize
 
-                 .anyRequest().authenticated()
 
-                 .anyExchange().permitAll()
 
-         }
 
-         .simpleAuthentication(withDefaults())
 
-     return rsocket.build()
 
- }
 
- ----
 
- ======
 
- The RSocket sender can send credentials using `SimpleAuthenticationEncoder` which can be added to Spring's `RSocketStrategies`.
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- RSocketStrategies.Builder strategies = ...;
 
- strategies.encoder(new SimpleAuthenticationEncoder());
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- var strategies: RSocketStrategies.Builder = ...
 
- strategies.encoder(SimpleAuthenticationEncoder())
 
- ----
 
- ======
 
- It can then be used to send a username and password to the receiver in the setup:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- MimeType authenticationMimeType =
 
- 	MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
 
- UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
 
- Mono<RSocketRequester> requester = RSocketRequester.builder()
 
- 	.setupMetadata(credentials, authenticationMimeType)
 
- 	.rsocketStrategies(strategies.build())
 
- 	.connectTcp(host, port);
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val authenticationMimeType: MimeType =
 
-     MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
 
- val credentials = UsernamePasswordMetadata("user", "password")
 
- val requester: Mono<RSocketRequester> = RSocketRequester.builder()
 
-     .setupMetadata(credentials, authenticationMimeType)
 
-     .rsocketStrategies(strategies.build())
 
-     .connectTcp(host, port)
 
- ----
 
- ======
 
- Alternatively or additionally, a username and password can be sent in a request.
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- 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, authenticationMimeType)
 
- 			.retrieveMono(AirportLocation.class)
 
- 	);
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- import org.springframework.messaging.rsocket.retrieveMono
 
- // ...
 
- var requester: Mono<RSocketRequester>? = null
 
- var credentials = UsernamePasswordMetadata("user", "password")
 
- open fun findRadar(code: String): Mono<AirportLocation> {
 
-     return requester!!.flatMap { req ->
 
-         req.route("find.radar.{code}", code)
 
-             .metadata(credentials, authenticationMimeType)
 
-             .retrieveMono<AirportLocation>()
 
-     }
 
- }
 
- ----
 
- ======
 
- [[rsocket-authentication-jwt]]
 
- === JWT
 
- 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.
 
- An example configuration can be found below:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
 
- 	rsocket
 
- 		.authorizePayload(authorize ->
 
- 			authorize
 
- 				.anyRequest().authenticated()
 
- 				.anyExchange().permitAll()
 
- 		)
 
- 		.jwt(Customizer.withDefaults());
 
- 	return rsocket.build();
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
 
-     rsocket
 
-         .authorizePayload { authorize -> authorize
 
-             .anyRequest().authenticated()
 
-             .anyExchange().permitAll()
 
-         }
 
-         .jwt(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:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- ReactiveJwtDecoder jwtDecoder() {
 
- 	return ReactiveJwtDecoders
 
- 		.fromIssuerLocation("https://example.com/auth/realms/demo");
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- fun jwtDecoder(): ReactiveJwtDecoder {
 
-     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:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- MimeType authenticationMimeType =
 
- 	MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
 
- BearerTokenMetadata token = ...;
 
- Mono<RSocketRequester> requester = RSocketRequester.builder()
 
- 	.setupMetadata(token, authenticationMimeType)
 
- 	.connectTcp(host, port);
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val authenticationMimeType: MimeType =
 
-     MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
 
- val token: BearerTokenMetadata = ...
 
- val requester = RSocketRequester.builder()
 
-     .setupMetadata(token, authenticationMimeType)
 
-     .connectTcp(host, port)
 
- ----
 
- ======
 
- Alternatively or additionally, the token can be sent in a request.
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- MimeType authenticationMimeType =
 
- 	MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
 
- Mono<RSocketRequester> requester;
 
- BearerTokenMetadata token = ...;
 
- public Mono<AirportLocation> findRadar(String code) {
 
- 	return this.requester.flatMap(req ->
 
- 		req.route("find.radar.{code}", code)
 
- 	        .metadata(token, authenticationMimeType)
 
- 			.retrieveMono(AirportLocation.class)
 
- 	);
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val authenticationMimeType: MimeType =
 
-     MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
 
- var requester: Mono<RSocketRequester>? = null
 
- val token: BearerTokenMetadata = ...
 
- open fun findRadar(code: String): Mono<AirportLocation> {
 
-     return this.requester!!.flatMap { req ->
 
-         req.route("find.radar.{code}", code)
 
-             .metadata(token, authenticationMimeType)
 
-             .retrieveMono<AirportLocation>()
 
-     }
 
- }
 
- ----
 
- ======
 
- [[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:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- rsocket
 
- 	.authorizePayload(authz ->
 
- 		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>
 
- 	);
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- rsocket
 
-     .authorizePayload { authz ->
 
-         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.
 
 
  |