Merge branch '5.8.x' into 6.0.x

Closes gh-13406
This commit is contained in:
Rob Winch
2023-06-18 21:33:58 -05:00
116 changed files with 4826 additions and 3206 deletions

View File

@@ -10,8 +10,10 @@ The easiest way to ensure that CORS is handled first is to use the `CorsWebFilte
Users can integrate the `CorsWebFilter` with Spring Security by providing a `CorsConfigurationSource`.
For example, the following will integrate CORS support within Spring Security:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -25,7 +27,8 @@ CorsConfigurationSource corsConfigurationSource() {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -38,12 +41,14 @@ fun corsConfigurationSource(): CorsConfigurationSource {
return source
}
----
====
======
The following will disable the CORS integration within Spring Security:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -55,7 +60,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -68,4 +74,4 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
}
----
====
======

View File

@@ -17,8 +17,10 @@ When an `ObservationRegistry` bean is present, Spring Security creates traces fo
For example, consider a simple Boot application:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@SpringBootApplication
@@ -45,7 +47,8 @@ public class MyApplication {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@SpringBootApplication
@@ -72,20 +75,17 @@ class MyApplication {
}
}
----
====
======
And a corresponding request:
====
[source,bash]
----
?> http -a user:password :8080
----
====
Will produce the following output (indentation added for clarity):
====
[source,bash]
----
START - name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@5dfdb78', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.00191856, duration(nanos)=1918560.0, startTimeNanos=101177265022745}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@121549e0']
@@ -101,15 +101,16 @@ START - name='http.server.requests', contextualName='null', error='null', lowCar
STOP - name='spring.security.http.chains', contextualName='spring.security.http.chains.after', error='null', lowCardinalityKeyValues=[chain.size='14', filter.section='after'], highCardinalityKeyValues=[request.line='/'], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@40b25623', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.001693146, duration(nanos)=1693146.0, startTimeNanos=101178044824275}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@3b6cec2']
STOP - name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@5dfdb78', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.784320641, duration(nanos)=7.84320641E8, startTimeNanos=101177265022745}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@121549e0']
----
====
[[webflux-observability-tracing-manual-configuration]]
=== Manual Configuration
For a non-Spring Boot application, or to override the existing Boot configuration, you can publish your own `ObservationRegistry` and Spring Security will still pick it up.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@SpringBootApplication
@@ -138,7 +139,8 @@ public class MyApplication {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@SpringBootApplication
@@ -167,7 +169,8 @@ class MyApplication {
}
----
.Xml
Xml::
+
[source,kotlin,role="secondary"]
----
<sec:http auto-config="true" observation-registry-ref="ref">
@@ -176,7 +179,7 @@ class MyApplication {
<!-- define and configure ObservationRegistry bean -->
----
====
======
[[webflux-observability-tracing-disable]]
=== Disabling Observability
@@ -186,8 +189,10 @@ However, this may turn off observations for more than just Spring Security.
Instead, you can alter the provided `ObservationRegistry` with an `ObservationPredicate` like the following:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -197,7 +202,8 @@ ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations(
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -206,7 +212,7 @@ fun noSpringSecurityObservations(): ObservationRegistryCustomizer<ObservationReg
(registry: ObservationRegistry) -> registry.observationConfig().observationPredicate(predicate)
}
----
====
======
[TIP]
There is no facility for disabling observations with XML support.

View File

@@ -14,8 +14,10 @@ The following example shows a minimal RSocket Security configuration:
You can find a minimal RSocket Security configuration below:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Configuration
@@ -34,7 +36,8 @@ public class HelloRSocketSecurityConfig {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Configuration
@@ -51,7 +54,7 @@ open class HelloRSocketSecurityConfig {
}
}
----
====
======
This configuration enables <<rsocket-authentication-simple,simple authentication>> and sets up <<rsocket-authorization,rsocket-authorization>> to require an authenticated user for any request.
@@ -61,8 +64,10 @@ For Spring Security to work, we need to apply `SecuritySocketAcceptorInterceptor
Doing so connects our `PayloadSocketAcceptorInterceptor` with the RSocket infrastructure.
In a Spring Boot application, you can do this automatically by using `RSocketSecurityAutoConfiguration` with the following code:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -71,7 +76,8 @@ RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInte
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -83,7 +89,7 @@ fun springSecurityRSocketSecurity(interceptor: SecuritySocketAcceptorInterceptor
}
}
----
====
======
[[rsocket-authentication]]
== RSocket Authentication
@@ -123,8 +129,10 @@ See `RSocketSecurity.basicAuthentication(Customizer)` for setting it up.
The RSocket receiver can decode the credentials by using `AuthenticationPayloadExchangeConverter`, which is automatically setup by using the `simpleAuthentication` portion of the DSL.
The following example shows an explicit configuration:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -140,7 +148,8 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -154,30 +163,35 @@ open fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInte
return rsocket.build()
}
----
====
======
The RSocket sender can send credentials by using `SimpleAuthenticationEncoder`, which you can add to Spring's `RSocketStrategies`.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
RSocketStrategies.Builder strategies = ...;
strategies.encoder(new SimpleAuthenticationEncoder());
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
var strategies: RSocketStrategies.Builder = ...
strategies.encoder(SimpleAuthenticationEncoder())
----
====
======
You can then use it to send a username and password to the receiver in the setup:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
MimeType authenticationMimeType =
@@ -189,7 +203,8 @@ Mono<RSocketRequester> requester = RSocketRequester.builder()
.connectTcp(host, port);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val authenticationMimeType: MimeType =
@@ -200,12 +215,14 @@ val requester: Mono<RSocketRequester> = RSocketRequester.builder()
.rsocketStrategies(strategies.build())
.connectTcp(host, port)
----
====
======
Alternatively or additionally, a username and password can be sent in a request.
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Mono<RSocketRequester> requester;
@@ -220,7 +237,8 @@ public Mono<AirportLocation> findRadar(String code) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.messaging.rsocket.retrieveMono
@@ -238,7 +256,7 @@ open fun findRadar(code: String): Mono<AirportLocation> {
}
}
----
====
======
[[rsocket-authentication-jwt]]
=== JWT
@@ -249,8 +267,10 @@ The support comes in the form of authenticating a JWT (determining that the JWT
The RSocket receiver can decode the credentials by using `BearerPayloadExchangeConverter`, which is automatically setup by using the `jwt` portion of the DSL.
The following listing shows an example configuration:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -266,7 +286,8 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -280,13 +301,15 @@ fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorIntercept
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:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -296,7 +319,8 @@ ReactiveJwtDecoder jwtDecoder() {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -305,13 +329,15 @@ fun jwtDecoder(): ReactiveJwtDecoder {
.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 a simple `String`.
The following example sends the token at setup time:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
MimeType authenticationMimeType =
@@ -322,7 +348,8 @@ Mono<RSocketRequester> requester = RSocketRequester.builder()
.connectTcp(host, port);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val authenticationMimeType: MimeType =
@@ -333,12 +360,14 @@ val requester = RSocketRequester.builder()
.setupMetadata(token, authenticationMimeType)
.connectTcp(host, port)
----
====
======
Alternatively or additionally, you can send the token in a request:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
MimeType authenticationMimeType =
@@ -355,7 +384,8 @@ public Mono<AirportLocation> findRadar(String code) {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val authenticationMimeType: MimeType =
@@ -371,7 +401,7 @@ open fun findRadar(code: String): Mono<AirportLocation> {
}
}
----
====
======
[[rsocket-authorization]]
== RSocket Authorization
@@ -380,8 +410,10 @@ RSocket authorization is performed with `AuthorizationPayloadInterceptor`, which
You can use the DSL to set up authorization rules based upon the `PayloadExchange`.
The following listing shows an example configuration:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
rsocket
@@ -397,7 +429,9 @@ rsocket
.anyExchange().permitAll() // <6>
);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
rsocket
@@ -413,6 +447,7 @@ rsocket
.anyExchange().permitAll()
} // <6>
----
======
<1> Setting up a connection requires the `ROLE_SETUP` authority.
<2> If the route is `fetch.profile.me`, authorization only requires the user to be authenticated.
<3> In this rule, we set up a custom matcher, where authorization requires the user to have the `ROLE_CUSTOM` authority.
@@ -424,7 +459,6 @@ 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 also have no authorization rules.
====
Note that authorization rules are performed in order.
Only the first authorization rule that matches is invoked.