# RSocket 安全性

Spring Security 的 RSocket 支持依赖于`SocketAcceptorInterceptor`。进入安全的主要入口点是`PayloadSocketAcceptorInterceptor`，它调整了 RSocket API，允许使用`PayloadInterceptor`实现来拦截`PayloadExchange`。

你可以找到几个示例应用程序来演示下面的代码：

* hello rsocket[HellorSocket](https://github.com/spring-projects/spring-security-samples/tree/5.6.x/reactive/rsocket/hello-security)

* [Spring Flights](https://github.com/rwinch/spring-flights/tree/security)

## 最小的 RSocket 安全配置

你可以在下面找到最小的 RSocket 安全配置：

爪哇

```
@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

```
@Configuration
@EnableRSocketSecurity
open class HelloRSocketSecurityConfig {
    @Bean
    open fun userDetailsService(): MapReactiveUserDetailsService {
        val user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("user")
            .roles("USER")
            .build()
        return MapReactiveUserDetailsService(user)
    }
}
```

此配置允许[简单的身份验证](#rsocket-authentication-simple)，并设置[RSocket-授权](#rsocket-authorization)以要求对任何请求进行身份验证的用户。

## 添加安全性：接收截取程序

为了使安全性工作，我们需要将`SecuritySocketAcceptorInterceptor`应用到`ServerRSocketFactory`。这就是将我们创建的`PayloadSocketAcceptorInterceptor`与 RSocket 基础架构连接起来的原因。在 Spring 引导应用程序中，这是使用`RSocketSecurityAutoConfiguration`和以下代码自动完成的。

```
@Bean
RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInterceptor interceptor) {
    return (server) -> server.interceptors((registry) -> registry.forSocketAcceptor(interceptor));
}
```

## RSocket 身份验证

RSocket 身份验证是通过`AuthenticationPayloadInterceptor`执行的，该控制器充当调用`ReactiveAuthenticationManager`实例的控制器。

### 设置时的身份验证与请求时的身份验证

通常，身份验证可以在设置时间和/或请求时间发生。

在几个场景中，设置时的身份验证是有意义的。一个常见的场景是当单个用户（即移动连接）利用 RSocket 连接时。在这种情况下，只有单个用户在利用连接，因此可以在连接时进行一次身份验证。

在共享 RSocket 连接的场景中，对每个请求发送凭据是有意义的。例如，作为下游服务连接到 RSocket 服务器的 Web 应用程序将生成一个所有用户都可以利用的单个连接。在这种情况下，如果 RSocket 服务器需要根据每个请求的 Web 应用程序的用户凭据来执行授权，这是有意义的。

在某些情况下，在设置和每个请求中进行身份验证是有意义的。考虑如前所述的 Web 应用程序。如果我们需要将连接限制到 Web 应用程序本身，那么我们可以在连接时提供一个具有`SETUP`权限的凭据。然后，每个用户将拥有不同的权限，但不是`SETUP`权限。这意味着个人用户可以提出请求，但不能建立额外的连接。

### 简单的身份验证

Spring 安全性支持[简单的身份验证元数据扩展](https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Simple.md)。

|   |基本的身份验证草案演变成简单的身份验证，并且只支持向后兼容性。<br/>设置它的方法请参见`RSocketSecurity.basicAuthentication(Customizer)`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

RSocket 接收器可以使用`AuthenticationPayloadExchangeConverter`来解码凭据，这是使用 DSL 的`simpleAuthentication`部分自动设置的。可以在下面找到一个显式配置。

爪哇

```
@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
	rsocket
		.authorizePayload(authorize ->
			authorize
					.anyRequest().authenticated()
					.anyExchange().permitAll()
		)
		.simpleAuthentication(Customizer.withDefaults());
	return rsocket.build();
}
```

Kotlin

```
@Bean
open fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
    rsocket
        .authorizePayload { authorize -> authorize
                .anyRequest().authenticated()
                .anyExchange().permitAll()
        }
        .simpleAuthentication(withDefaults())
    return rsocket.build()
}
```

RSocket 发送者可以使用`SimpleAuthenticationEncoder`发送凭据，该凭据可以添加到 Spring 的`RSocketStrategies`中。

爪哇

```
RSocketStrategies.Builder strategies = ...;
strategies.encoder(new SimpleAuthenticationEncoder());
```

Kotlin

```
var strategies: RSocketStrategies.Builder = ...
strategies.encoder(SimpleAuthenticationEncoder())
```

然后可以使用它向设置中的接收器发送用户名和密码：

爪哇

```
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

```
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)
```

另外，也可以在请求中发送用户名和密码。

爪哇

```
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

```
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>()
    }
}
```

### JWT

Spring 安全性支持[承载令牌身份验证元数据扩展](https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Bearer.md)。支持的形式是对 JWT 进行身份验证（确定 JWT 是有效的），然后使用 JWT 做出授权决策。

RSocket 接收器可以使用`BearerPayloadExchangeConverter`来解码凭据，这是使用 DSL 的`jwt`部分自动设置的。下面是一个配置示例：

爪哇

```
@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
	rsocket
		.authorizePayload(authorize ->
			authorize
				.anyRequest().authenticated()
				.anyExchange().permitAll()
		)
		.jwt(Customizer.withDefaults());
	return rsocket.build();
}
```

Kotlin

```
@Bean
fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
    rsocket
        .authorizePayload { authorize -> authorize
            .anyRequest().authenticated()
            .anyExchange().permitAll()
        }
        .jwt(withDefaults())
    return rsocket.build()
}
```

上述配置依赖于存在`ReactiveJwtDecoder``@Bean`。可以在下面找到从发行者创建一个发行者的示例：

爪哇

```
@Bean
ReactiveJwtDecoder jwtDecoder() {
	return ReactiveJwtDecoders
		.fromIssuerLocation("https://example.com/auth/realms/demo");
}
```

Kotlin

```
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
    return ReactiveJwtDecoders
        .fromIssuerLocation("https://example.com/auth/realms/demo")
}
```

RSocket 发送者不需要做任何特殊的事情来发送令牌，因为该值只是一个简单的字符串。例如，可以在设置时发送令牌：

爪哇

```
MimeType authenticationMimeType =
	MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
BearerTokenMetadata token = ...;
Mono<RSocketRequester> requester = RSocketRequester.builder()
	.setupMetadata(token, authenticationMimeType)
	.connectTcp(host, port);
```

Kotlin

```
val authenticationMimeType: MimeType =
    MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
val token: BearerTokenMetadata = ...

val requester = RSocketRequester.builder()
    .setupMetadata(token, authenticationMimeType)
    .connectTcp(host, port)
```

可选地或附加地，令牌可以在请求中发送。

爪哇

```
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

```
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 授权

RSocket 授权是用`AuthorizationPayloadInterceptor`执行的，它作为一个控制器来调用`ReactiveAuthorizationManager`实例。DSL 可用于基于`PayloadExchange`设置授权规则。下面是一个配置示例：

爪哇

```
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

```
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**|建立连接需要授权`ROLE_SETUP`|
|-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|如果路由是`fetch.profile.me`，则授权只需要对用户进行身份验证即可。|
|**3**|在这个规则中，我们设置了一个自定义的匹配器，其中授权要求用户拥有`ROLE_CUSTOM`的权限|
|**4**|该规则利用自定义授权。<br/>Matcher 表示一个名为`username`的变量，该变量在`context`中可用。<br/>在`checkFriends`方法中公开了一个自定义授权规则。|
|**5**|此规则确保尚未具有规则的请求将需要对用户进行身份验证。<br/>包含元数据的位置是一个请求。<br/>它将不包括额外的有效负载。|
|**6**|此规则确保任何人都可以使用任何尚未拥有规则的交换。<br/>在本例中，这意味着没有元数据的有效负载没有授权规则。|

重要的是要理解授权规则是按顺序执行的。将只调用匹配的第一个授权规则。

[CORS](cors.html)[Testing](../test/index.html)