Merge branch '5.8.x' into 6.0.x
Closes gh-13406
This commit is contained in:
@@ -11,7 +11,10 @@ This will:
|
||||
Often, you will want to also invalidate the session on logout.
|
||||
To achieve this, you can add the `WebSessionServerLogoutHandler` to your logout configuration, like so:
|
||||
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -28,7 +31,8 @@ SecurityWebFilterChain http(ServerHttpSecurity http) throws Exception {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -47,3 +51,4 @@ fun http(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -5,8 +5,10 @@ Similar to xref:servlet/authentication/x509.adoc#servlet-x509[Servlet X.509 auth
|
||||
|
||||
The following example shows a reactive x509 security configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -20,7 +22,8 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -33,14 +36,16 @@ fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used. The default principal extractor is `SubjectDnX509PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client. The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
|
||||
|
||||
The following example demonstrates how these defaults can be overridden:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -67,7 +72,8 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -89,7 +95,7 @@ fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In the previous example, a username is extracted from the OU field of a client certificate instead of CN, and account lookup using `ReactiveUserDetailsService` is not performed at all. Instead, if the provided certificate issued to an OU named "`Trusted Org Unit`", a request is authenticated.
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@ By default, Spring Security’s authorization will require all requests to be au
|
||||
The explicit configuration looks like:
|
||||
|
||||
.All Requests Require Authenticated User
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -22,7 +24,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -36,14 +39,16 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
We can configure Spring Security to have different rules by adding more rules in order of precedence.
|
||||
|
||||
.Multiple Authorize Requests Rules
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;
|
||||
@@ -68,7 +73,8 @@ SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -88,7 +94,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
<1> There are multiple authorization rules specified.
|
||||
Each rule is considered in the order they were declared.
|
||||
|
||||
@@ -32,8 +32,10 @@ For earlier versions, please read about similar support with <<jc-enable-reactiv
|
||||
For example, the following would enable Spring Security's `@PreAuthorize` annotation:
|
||||
|
||||
.Method Security Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
|
||||
@@ -41,15 +43,17 @@ public class MethodSecurityConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Adding an annotation to a method (on a class or interface) would then limit the access to that method accordingly.
|
||||
Spring Security's native annotation support defines a set of attributes for the method.
|
||||
These will be passed to the various method interceptors, like `AuthorizationManagerBeforeReactiveMethodInterceptor`, for it to make the actual decision:
|
||||
|
||||
.Method Security Annotation Usage
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public interface BankService {
|
||||
@@ -63,7 +67,7 @@ public interface BankService {
|
||||
Mono<Account> post(Account account, Double amount);
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In this case `hasRole` refers to the method found in `SecurityExpressionRoot` that gets invoked by the SpEL evaluation engine.
|
||||
|
||||
@@ -71,8 +75,10 @@ In this case `hasRole` refers to the method found in `SecurityExpressionRoot` th
|
||||
A bean like that might look something like this:
|
||||
|
||||
.Method Security Reactive Boolean Expression
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -80,20 +86,22 @@ public Function<Account, Mono<Boolean>> func() {
|
||||
return (account) -> Mono.defer(() -> Mono.just(account.getId().equals(12)));
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Customizing Authorization
|
||||
|
||||
Spring Security's `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` ship with rich expression-based support.
|
||||
|
||||
[[jc-reactive-method-security-custom-granted-authority-defaults]]
|
||||
|
||||
[[jc-reactive-method-security-custom-granted-authority-defaults]]
|
||||
Also, for role-based authorization, Spring Security adds a default `ROLE_` prefix, which is uses when evaluating expressions like `hasRole`.
|
||||
You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
|
||||
|
||||
.Custom MethodSecurityExpressionHandler
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -101,7 +109,7 @@ static GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
||||
return new GrantedAuthorityDefaults("MYPREFIX_");
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
@@ -124,8 +132,10 @@ If that authorization denies access, the value is not returned, and an `AccessDe
|
||||
To recreate what adding `@EnableReactiveMethodSecurity(useAuthorizationManager=true)` does by default, you would publish the following configuration:
|
||||
|
||||
.Full Pre-post Method Security Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -160,15 +170,17 @@ class MethodSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Notice that Spring Security's method security is built using Spring AOP.
|
||||
So, interceptors are invoked based on the order specified.
|
||||
This can be customized by calling `setOrder` on the interceptor instances like so:
|
||||
|
||||
.Publish Custom Advisor
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -179,13 +191,15 @@ Advisor postFilterAuthorizationMethodInterceptor() {
|
||||
return interceptor;
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You may want to only support `@PreAuthorize` in your application, in which case you can do the following:
|
||||
|
||||
.Only @PreAuthorize Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -202,7 +216,7 @@ class MethodSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Or, you may have a custom before-method `ReactiveAuthorizationManager` that you want to add to the list.
|
||||
|
||||
@@ -211,9 +225,11 @@ In this case, you will need to tell Spring Security both the `ReactiveAuthorizat
|
||||
Thus, you can configure Spring Security to invoke your `ReactiveAuthorizationManager` in between `@PreAuthorize` and `@PostAuthorize` like so:
|
||||
|
||||
.Custom Before Advisor
|
||||
====
|
||||
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
|
||||
@@ -230,7 +246,7 @@ class MethodSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
@@ -243,8 +259,10 @@ After-method authorization is generally concerned with analysing the return valu
|
||||
For example, you might have a method that confirms that the account requested actually belongs to the logged-in user like so:
|
||||
|
||||
.@PostAuthorize example
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public interface BankService {
|
||||
@@ -254,7 +272,7 @@ public interface BankService {
|
||||
Mono<Account> readAccount(Long id);
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can supply your own `AuthorizationMethodInterceptor` to customize how access to the return value is evaluated.
|
||||
|
||||
@@ -262,8 +280,10 @@ For example, if you have your own custom annotation, you can configure it like s
|
||||
|
||||
|
||||
.Custom After Advisor
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
|
||||
@@ -278,7 +298,7 @@ class MethodSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
and it will be invoked after the `@PostAuthorize` interceptor.
|
||||
|
||||
@@ -291,8 +311,10 @@ When intercepting coroutines, only the first interceptor participates.
|
||||
If any other interceptors are present and come after Spring Security's method security interceptor, https://github.com/spring-projects/spring-framework/issues/22462[they will be skipped].
|
||||
====
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
@@ -309,7 +331,8 @@ StepVerifier.create(messageByUsername)
|
||||
.verifyComplete();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
|
||||
@@ -324,12 +347,14 @@ StepVerifier.create(messageByUsername)
|
||||
.expectNext("Hi user")
|
||||
.verifyComplete()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Where `this::findMessageByUsername` is defined as:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Mono<String> findMessageByUsername(String username) {
|
||||
@@ -337,19 +362,22 @@ Mono<String> findMessageByUsername(String username) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
fun findMessageByUsername(username: String): Mono<String> {
|
||||
return Mono.just("Hi $username")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following minimal method security configures method security in reactive applications:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -371,7 +399,8 @@ public class SecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -392,12 +421,14 @@ class SecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Consider the following class:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
@@ -409,7 +440,8 @@ public class HelloWorldMessageService {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
@@ -420,12 +452,14 @@ class HelloWorldMessageService {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Alternatively, the following class uses Kotlin coroutines:
|
||||
|
||||
====
|
||||
.Kotlin
|
||||
[tabs]
|
||||
======
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="primary"]
|
||||
----
|
||||
@Component
|
||||
@@ -437,7 +471,7 @@ class HelloWorldMessageService {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
Combined with our configuration above, `@PreAuthorize("hasRole('ADMIN')")` ensures that `findByMessage` is invoked only by a user with the `ADMIN` role.
|
||||
@@ -447,8 +481,10 @@ This means that the expression must not block.
|
||||
|
||||
When integrating with xref:reactive/configuration/webflux.adoc#jc-webflux[WebFlux Security], the Reactor Context is automatically established by Spring Security according to the authenticated user:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -484,7 +520,8 @@ public class SecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -516,6 +553,6 @@ class SecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can find a complete sample in {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method].
|
||||
|
||||
@@ -14,8 +14,10 @@ A few sample applications demonstrate the code:
|
||||
The following listing shows a minimal WebFlux Security configuration:
|
||||
|
||||
.Minimal WebFlux Security Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
-----
|
||||
@Configuration
|
||||
@@ -34,7 +36,8 @@ public class HelloWebfluxSecurityConfig {
|
||||
}
|
||||
-----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
-----
|
||||
@Configuration
|
||||
@@ -52,7 +55,7 @@ class HelloWebfluxSecurityConfig {
|
||||
}
|
||||
}
|
||||
-----
|
||||
====
|
||||
======
|
||||
|
||||
This configuration provides form and HTTP basic authentication, sets up authorization to require an authenticated user for accessing any page, sets up a default login page and a default logout page, sets up security related HTTP headers, adds CSRF protection, and more.
|
||||
|
||||
@@ -61,8 +64,10 @@ This configuration provides form and HTTP basic authentication, sets up authoriz
|
||||
The following page shows an explicit version of the minimal WebFlux Security configuration:
|
||||
|
||||
.Explicit WebFlux Security Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
-----
|
||||
@Configuration
|
||||
@@ -92,7 +97,8 @@ public class HelloWebfluxSecurityConfig {
|
||||
}
|
||||
-----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
-----
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
@@ -123,7 +129,7 @@ class HelloWebfluxSecurityConfig {
|
||||
}
|
||||
}
|
||||
-----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Make sure that you import the `invoke` function in your Kotlin class, sometimes the IDE will not auto-import it causing compilation issues.
|
||||
@@ -140,8 +146,10 @@ You can configure multiple `SecurityWebFilterChain` instances to separate config
|
||||
|
||||
For example, you can isolate configuration for URLs that start with `/api`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -179,7 +187,8 @@ static class MultiSecurityHttpConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
@@ -219,13 +228,13 @@ open class MultiSecurityHttpConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
<1> Configure a `SecurityWebFilterChain` with an `@Order` to specify which `SecurityWebFilterChain` Spring Security should consider first
|
||||
<2> Use `PathPatternParserServerWebExchangeMatcher` to state that this `SecurityWebFilterChain` will only apply to URL paths that start with `/api/`
|
||||
<3> Specify the authentication mechanisms that will be used for `/api/**` endpoints
|
||||
<4> Create another instance of `SecurityWebFilterChain` with lower precedence to match all other URLs
|
||||
<5> Specify the authentication mechanisms that will be used for the rest of the application
|
||||
====
|
||||
|
||||
Spring Security selects one `SecurityWebFilterChain` `@Bean` for each request.
|
||||
It matches the requests in order by the `securityMatcher` definition.
|
||||
|
||||
@@ -35,8 +35,10 @@ These defaults come from https://docs.angularjs.org/api/ng/service/$http#cross-s
|
||||
You can configure `CookieServerCsrfTokenRepository` in Java Configuration:
|
||||
|
||||
.Store CSRF Token in a Cookie
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
-----
|
||||
@Bean
|
||||
@@ -48,7 +50,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
|
||||
}
|
||||
-----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
-----
|
||||
@Bean
|
||||
@@ -61,7 +64,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
-----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -78,8 +81,10 @@ However, you can disable CSRF protection if it xref:features/exploits/csrf.adoc#
|
||||
The Java configuration below will disable CSRF protection.
|
||||
|
||||
.Disable CSRF Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -91,7 +96,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
-----
|
||||
@Bean
|
||||
@@ -104,7 +110,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
-----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-csrf-configure-request-handler]]
|
||||
==== Configure ServerCsrfTokenRequestHandler
|
||||
@@ -117,8 +123,10 @@ As of 6.0, the default implementation is `XorServerCsrfTokenRequestAttributeHand
|
||||
If you wish to disable BREACH protection of the `CsrfToken` and revert to the 5.8 default, you can configure `ServerCsrfTokenRequestAttributeHandler` using the following Java configuration:
|
||||
|
||||
.Disable BREACH protection
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
-----
|
||||
@Bean
|
||||
@@ -132,7 +140,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
|
||||
}
|
||||
-----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
-----
|
||||
@Bean
|
||||
@@ -145,7 +154,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
-----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-csrf-include]]
|
||||
=== Include the CSRF Token
|
||||
@@ -161,8 +170,10 @@ If your view technology does not provide a simple way to subscribe to the `Mono<
|
||||
The following example places the `CsrfToken` on the default attribute name (`_csrf`) used by Spring Security's <<webflux-csrf-include-form-auto,CsrfRequestDataValueProcessor>> to automatically include the CSRF token as a hidden input:
|
||||
|
||||
.`CsrfToken` as `@ModelAttribute`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ControllerAdvice
|
||||
@@ -176,7 +187,8 @@ public class SecurityControllerAdvice {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ControllerAdvice
|
||||
@@ -190,7 +202,7 @@ class SecurityControllerAdvice {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Fortunately, Thymeleaf provides <<webflux-csrf-include-form-auto,integration>> that works without any additional work.
|
||||
|
||||
@@ -200,14 +212,12 @@ To post an HTML form, the CSRF token must be included in the form as a hidden in
|
||||
The following example shows what the rendered HTML might look like:
|
||||
|
||||
.CSRF Token HTML
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<input type="hidden"
|
||||
name="_csrf"
|
||||
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
|
||||
----
|
||||
====
|
||||
|
||||
Next, we discuss various ways of including the CSRF token in a form as a hidden input.
|
||||
|
||||
@@ -227,7 +237,6 @@ If the <<webflux-csrf-include,other options>> for including the actual CSRF toke
|
||||
The following Thymeleaf sample assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`:
|
||||
|
||||
.CSRF Token in Form with Request Attribute
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<form th:action="@{/logout}"
|
||||
@@ -239,7 +248,6 @@ The following Thymeleaf sample assumes that you <<webflux-csrf-include-subscribe
|
||||
th:value="${_csrf.token}"/>
|
||||
</form>
|
||||
----
|
||||
====
|
||||
|
||||
[[webflux-csrf-include-ajax]]
|
||||
==== Ajax and JSON Requests
|
||||
@@ -261,7 +269,6 @@ An alternative pattern to <<webflux-csrf-include-form-auto,exposing the CSRF in
|
||||
The HTML might look something like this:
|
||||
|
||||
.CSRF meta tag HTML
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<html>
|
||||
@@ -272,13 +279,11 @@ The HTML might look something like this:
|
||||
</head>
|
||||
<!-- ... -->
|
||||
----
|
||||
====
|
||||
|
||||
Once the meta tags contain the CSRF token, the JavaScript code can read the meta tags and include the CSRF token as a header.
|
||||
If you use jQuery, you could read the meta tags with the following code:
|
||||
|
||||
.AJAX send CSRF Token
|
||||
====
|
||||
[source,javascript]
|
||||
----
|
||||
$(function () {
|
||||
@@ -289,13 +294,11 @@ $(function () {
|
||||
});
|
||||
});
|
||||
----
|
||||
====
|
||||
|
||||
The following sample assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`.
|
||||
The following example does this with Thymeleaf:
|
||||
|
||||
.CSRF meta tag JSP
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<html>
|
||||
@@ -307,7 +310,6 @@ The following example does this with Thymeleaf:
|
||||
</head>
|
||||
<!-- ... -->
|
||||
----
|
||||
====
|
||||
|
||||
[[webflux-csrf-considerations]]
|
||||
== CSRF Considerations
|
||||
@@ -339,8 +341,10 @@ For example, the following Java Configuration logs out when the `/logout` URL is
|
||||
// FIXME: This should be a link to log out documentation
|
||||
|
||||
.Log out with HTTP GET
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -352,7 +356,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -365,7 +370,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-considerations-csrf-timeouts]]
|
||||
@@ -401,8 +406,10 @@ We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart[already d
|
||||
In a WebFlux application, you can do so with the following configuration:
|
||||
|
||||
.Enable obtaining CSRF token from multipart/form-data
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -414,7 +421,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -427,7 +435,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-csrf-considerations-multipart-url]]
|
||||
==== Include CSRF Token in URL
|
||||
@@ -437,14 +445,12 @@ Since the `CsrfToken` is exposed as an `ServerHttpRequest` <<webflux-csrf-includ
|
||||
An example with Thymeleaf is shown below:
|
||||
|
||||
.CSRF Token in Action
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<form method="post"
|
||||
th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
|
||||
enctype="multipart/form-data">
|
||||
----
|
||||
====
|
||||
|
||||
[[webflux-csrf-considerations-override-method]]
|
||||
=== HiddenHttpMethodFilter
|
||||
|
||||
@@ -16,8 +16,10 @@ For example, assume that you want the defaults but you wish to specify `SAMEORIG
|
||||
You can do so with the following configuration:
|
||||
|
||||
.Customize Default Security Headers
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -33,7 +35,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -48,14 +51,16 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If you do not want the defaults to be added and want explicit control over what should be used, you can disable the defaults:
|
||||
|
||||
|
||||
.Disable HTTP Security Response Headers
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -67,7 +72,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -80,7 +86,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-cache-control]]
|
||||
== Cache Control
|
||||
@@ -96,8 +102,10 @@ You can find details on how to do so in the https://docs.spring.io/spring/docs/5
|
||||
If necessary, you can also disable Spring Security's cache control HTTP response headers.
|
||||
|
||||
.Cache Control Disabled
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -111,7 +119,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -126,7 +135,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-headers-content-type-options]]
|
||||
@@ -135,8 +144,10 @@ By default, Spring Security includes xref:features/exploits/headers.adoc#headers
|
||||
However, you can disable it:
|
||||
|
||||
.Content Type Options Disabled
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -150,7 +161,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -165,7 +177,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-hsts]]
|
||||
== HTTP Strict Transport Security (HSTS)
|
||||
@@ -174,8 +186,10 @@ However, you can customize the results explicitly.
|
||||
For example, the following example explicitly provides HSTS:
|
||||
|
||||
.Strict Transport Security
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -193,7 +207,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -210,7 +225,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-frame-options]]
|
||||
== X-Frame-Options
|
||||
@@ -219,8 +234,10 @@ By default, Spring Security disables rendering within an iframe by using xref:fe
|
||||
You can customize frame options to use the same origin:
|
||||
|
||||
.X-Frame-Options: SAMEORIGIN
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -236,7 +253,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -251,7 +269,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-xss-protection]]
|
||||
== X-XSS-Protection
|
||||
@@ -259,8 +277,10 @@ By default, Spring Security instructs browsers to disable the XSS Auditor by usi
|
||||
You can disable the `X-XSS-Protection` header entirely:
|
||||
|
||||
.X-XSS-Protection Customization
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -274,7 +294,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -289,13 +310,15 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can also change the header value:
|
||||
|
||||
.X-XSS-Protection Explicit header value
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -309,7 +332,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -324,7 +348,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-csp]]
|
||||
== Content Security Policy (CSP)
|
||||
@@ -334,18 +358,18 @@ The web application author must declare the security policies to enforce and/or
|
||||
For example, consider the following security policy:
|
||||
|
||||
.Content Security Policy Example
|
||||
====
|
||||
[source,http]
|
||||
----
|
||||
Content-Security-Policy: script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/
|
||||
----
|
||||
====
|
||||
|
||||
Given the preceding policy, you can enable the CSP header:
|
||||
|
||||
.Content Security Policy
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -361,7 +385,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -376,13 +401,15 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
To enable the CSP `report-only` header, provide the following configuration:
|
||||
|
||||
.Content Security Policy Report Only
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -399,7 +426,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -415,7 +443,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-referrer]]
|
||||
== Referrer Policy
|
||||
@@ -424,8 +452,10 @@ By default, Spring Security does not add xref:features/exploits/headers.adoc#hea
|
||||
You can enable the Referrer Policy header using configuration as shown below:
|
||||
|
||||
.Referrer Policy Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -441,7 +471,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -456,7 +487,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-headers-feature]]
|
||||
@@ -466,18 +497,18 @@ By default, Spring Security does not add xref:features/exploits/headers.adoc#hea
|
||||
Consider the following `Feature-Policy` header:
|
||||
|
||||
.Feature-Policy Example
|
||||
====
|
||||
[source]
|
||||
----
|
||||
Feature-Policy: geolocation 'self'
|
||||
----
|
||||
====
|
||||
|
||||
You can enable the preceding Feature Policy header:
|
||||
|
||||
.Feature-Policy Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -491,7 +522,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -504,7 +536,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-headers-permissions]]
|
||||
@@ -514,18 +546,18 @@ By default, Spring Security does not add xref:features/exploits/headers.adoc#hea
|
||||
Consider the following `Permissions-Policy` header:
|
||||
|
||||
.Permissions-Policy Example
|
||||
====
|
||||
[source]
|
||||
----
|
||||
Permissions-Policy: geolocation=(self)
|
||||
----
|
||||
====
|
||||
|
||||
You can enable the preceding Permissions Policy header:
|
||||
|
||||
.Permissions-Policy Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -541,7 +573,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -556,7 +589,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-headers-clear-site-data]]
|
||||
@@ -566,17 +599,17 @@ By default, Spring Security does not add xref:features/exploits/headers.adoc#hea
|
||||
Consider the following `Clear-Site-Data` header:
|
||||
|
||||
.Clear-Site-Data Example
|
||||
====
|
||||
----
|
||||
Clear-Site-Data: "cache", "cookies"
|
||||
----
|
||||
====
|
||||
|
||||
You can send the `Clear-Site-Data` header on logout:
|
||||
|
||||
.Clear-Site-Data Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -594,7 +627,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -612,4 +646,4 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
@@ -13,8 +13,10 @@ If a client makes a request using HTTP rather than HTTPS, you can configure Spri
|
||||
The following Java configuration redirects any HTTP requests to HTTPS:
|
||||
|
||||
.Redirect to HTTPS
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -26,7 +28,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -37,15 +40,17 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can wrap the configuration can be wrapped around an `if` statement to be turned on only in production.
|
||||
Alternatively, you can enable it by looking for a property about the request that happens only in production.
|
||||
For example, if the production environment adds a header named `X-Forwarded-Proto`, you should use the following Java Configuration:
|
||||
|
||||
.Redirect to HTTPS when X-Forwarded
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -59,7 +64,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -74,7 +80,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-hsts]]
|
||||
== Strict Transport Security
|
||||
|
||||
@@ -14,8 +14,10 @@ For your convenience, you can download a minimal Reactive Spring Boot + Spring S
|
||||
|
||||
You can add Spring Security to your Spring Boot project by adding `spring-boot-starter-security`.
|
||||
|
||||
====
|
||||
.Maven
|
||||
[tabs]
|
||||
======
|
||||
Maven::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<dependency>
|
||||
@@ -24,12 +26,13 @@ You can add Spring Security to your Spring Boot project by adding `spring-boot-s
|
||||
</dependency>
|
||||
----
|
||||
|
||||
.Gradle
|
||||
Gradle::
|
||||
+
|
||||
[source,groovy,role="secondary"]
|
||||
----
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[servlet-hello-starting]]
|
||||
@@ -38,10 +41,12 @@ You can add Spring Security to your Spring Boot project by adding `spring-boot-s
|
||||
You can now https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-running-with-the-maven-plugin[run the Spring Boot application] by using the Maven Plugin's `run` goal.
|
||||
The following example shows how to do so (and the beginning of the output from doing so):
|
||||
|
||||
.Running Spring Boot Application
|
||||
|
||||
====
|
||||
.Maven
|
||||
.Running Spring Boot Application
|
||||
[tabs]
|
||||
======
|
||||
Maven::
|
||||
+
|
||||
[source,bash,role="primary"]
|
||||
----
|
||||
$ ./mvnw spring-boot:run
|
||||
@@ -53,7 +58,8 @@ Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
|
||||
...
|
||||
----
|
||||
|
||||
.Gradle
|
||||
Gradle::
|
||||
+
|
||||
[source,bash,role="secondary"]
|
||||
----
|
||||
$ ./gradlew bootRun
|
||||
@@ -64,7 +70,7 @@ Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
|
||||
|
||||
...
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[authenticating]]
|
||||
== Authenticating
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -111,8 +111,10 @@ OPTIONAL. Space delimited, case sensitive list of ASCII string values that speci
|
||||
|
||||
The following example shows how to configure the `DefaultServerOAuth2AuthorizationRequestResolver` with a `Consumer<OAuth2AuthorizationRequest.Builder>` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -155,7 +157,8 @@ public class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -196,7 +199,7 @@ class SecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
For the simple use case, where the additional request parameter is always the same for a specific provider, it may be added directly in the `authorization-uri` property.
|
||||
|
||||
@@ -221,8 +224,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
||||
|
||||
The following example shows a variation of `authorizationRequestCustomizer()` from the preceding example, and instead overrides the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
|
||||
@@ -232,7 +237,8 @@ private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomi
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
|
||||
@@ -245,7 +251,7 @@ private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationReques
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
=== Storing the Authorization Request
|
||||
@@ -260,8 +266,10 @@ The default implementation of `ServerAuthorizationRequestRepository` is `WebSess
|
||||
If you have a custom implementation of `ServerAuthorizationRequestRepository`, you may configure it as shown in the following example:
|
||||
|
||||
.ServerAuthorizationRequestRepository Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -280,7 +288,8 @@ public class OAuth2ClientSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -299,7 +308,7 @@ class OAuth2ClientSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Requesting an Access Token
|
||||
|
||||
@@ -335,8 +344,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
||||
Whether you customize `WebClientReactiveAuthorizationCodeTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you’ll need to configure it as shown in the following example:
|
||||
|
||||
.Access Token Response Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -363,7 +374,8 @@ public class OAuth2ClientSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -389,7 +401,7 @@ class OAuth2ClientSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[oauth2Client-refresh-token-grant]]
|
||||
@@ -433,8 +445,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
||||
Whether you customize `WebClientReactiveRefreshTokenTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you’ll need to configure it as shown in the following example:
|
||||
|
||||
.Access Token Response Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
@@ -451,7 +465,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
@@ -466,7 +481,7 @@ val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveO
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().refreshToken()` configures a `RefreshTokenReactiveOAuth2AuthorizedClientProvider`,
|
||||
@@ -516,8 +531,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
||||
|
||||
Whether you customize `WebClientReactiveClientCredentialsTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
@@ -533,7 +550,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
@@ -547,7 +565,7 @@ val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveO
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().clientCredentials()` configures a `ClientCredentialsReactiveOAuth2AuthorizedClientProvider`,
|
||||
@@ -576,8 +594,10 @@ spring:
|
||||
|
||||
...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -599,7 +619,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -615,12 +636,14 @@ fun authorizedClientManager(
|
||||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
@@ -644,7 +667,8 @@ public class OAuth2ClientController {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class OAuth2ClientController {
|
||||
@@ -666,7 +690,7 @@ class OAuth2ClientController {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`ServerWebExchange` is an OPTIONAL attribute.
|
||||
@@ -713,8 +737,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
||||
|
||||
Whether you customize `WebClientReactivePasswordTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
@@ -731,7 +757,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val passwordTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...
|
||||
@@ -745,7 +772,7 @@ val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.bui
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordReactiveOAuth2AuthorizedClientProvider`,
|
||||
@@ -774,8 +801,10 @@ spring:
|
||||
|
||||
...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -819,7 +848,9 @@ private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttri
|
||||
};
|
||||
}
|
||||
----
|
||||
.Kotlin
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -858,12 +889,14 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<Mut
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
@@ -887,7 +920,8 @@ public class OAuth2ClientController {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
@@ -909,7 +943,7 @@ class OAuth2ClientController {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`ServerWebExchange` is an OPTIONAL attribute.
|
||||
@@ -955,8 +989,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
||||
|
||||
Whether you customize `WebClientReactiveJwtBearerTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
@@ -975,7 +1011,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
@@ -992,7 +1029,7 @@ val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.bui
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Using the Access Token
|
||||
|
||||
@@ -1017,8 +1054,10 @@ spring:
|
||||
|
||||
...and the `OAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -1043,7 +1082,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -1060,12 +1100,14 @@ fun authorizedClientManager(
|
||||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RestController
|
||||
@@ -1087,7 +1129,8 @@ public class OAuth2ResourceServerController {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class OAuth2ResourceServerController {
|
||||
@@ -1106,7 +1149,7 @@ class OAuth2ResourceServerController {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`JwtBearerReactiveOAuth2AuthorizedClientProvider` resolves the `Jwt` assertion via `OAuth2AuthorizationContext.getPrincipal().getPrincipal()` by default, hence the use of `JwtAuthenticationToken` in the preceding example.
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
The `@RegisteredOAuth2AuthorizedClient` annotation provides the capability of resolving a method parameter to an argument value of type `OAuth2AuthorizedClient`.
|
||||
This is a convenient alternative compared to accessing the `OAuth2AuthorizedClient` using the `ReactiveOAuth2AuthorizedClientManager` or `ReactiveOAuth2AuthorizedClientService`.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
@@ -24,7 +26,8 @@ public class OAuth2ClientController {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
@@ -37,7 +40,7 @@ class OAuth2ClientController {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The `@RegisteredOAuth2AuthorizedClient` annotation is handled by `OAuth2AuthorizedClientArgumentResolver`, which directly uses a <<oauth2Client-authorized-manager-provider, ReactiveOAuth2AuthorizedClientManager>> and therefore inherits it's capabilities.
|
||||
|
||||
@@ -58,8 +61,10 @@ It directly uses an <<oauth2Client-authorized-manager-provider, ReactiveOAuth2Au
|
||||
|
||||
The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -72,7 +77,8 @@ WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManage
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -83,7 +89,7 @@ fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): W
|
||||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Providing the Authorized Client
|
||||
|
||||
@@ -91,8 +97,10 @@ The `ServerOAuth2AuthorizedClientExchangeFilterFunction` determines the client t
|
||||
|
||||
The following code shows how to set an `OAuth2AuthorizedClient` as a request attribute:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
@@ -110,7 +118,8 @@ public Mono<String> index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2Author
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
@@ -127,14 +136,16 @@ fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2Auth
|
||||
.thenReturn("index")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
<1> `oauth2AuthorizedClient()` is a `static` method in `ServerOAuth2AuthorizedClientExchangeFilterFunction`.
|
||||
|
||||
The following code shows how to set the `ClientRegistration.getRegistrationId()` as a request attribute:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
@@ -152,7 +163,8 @@ public Mono<String> index() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
@@ -169,7 +181,7 @@ fun index(): Mono<String> {
|
||||
.thenReturn("index")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
<1> `clientRegistrationId()` is a `static` method in `ServerOAuth2AuthorizedClientExchangeFilterFunction`.
|
||||
|
||||
|
||||
@@ -181,8 +193,10 @@ If `setDefaultOAuth2AuthorizedClient(true)` is configured and the user has authe
|
||||
|
||||
The following code shows the specific configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -196,7 +210,8 @@ WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManage
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -208,7 +223,7 @@ fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): W
|
||||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[WARNING]
|
||||
It is recommended to be cautious with this feature since all HTTP requests will receive the access token.
|
||||
@@ -217,8 +232,10 @@ Alternatively, if `setDefaultClientRegistrationId("okta")` is configured with a
|
||||
|
||||
The following code shows the specific configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -232,7 +249,8 @@ WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManage
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -244,7 +262,7 @@ fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): W
|
||||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[WARNING]
|
||||
It is recommended to be cautious with this feature since all HTTP requests will receive the access token.
|
||||
|
||||
@@ -36,8 +36,10 @@ spring:
|
||||
|
||||
The following example shows how to configure `WebClientReactiveAuthorizationCodeTokenResponseClient`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
|
||||
@@ -59,7 +61,8 @@ tokenResponseClient.addParametersConverter(
|
||||
new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val jwkResolver: Function<ClientRegistration, JWK> =
|
||||
@@ -81,7 +84,7 @@ tokenResponseClient.addParametersConverter(
|
||||
NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
|
||||
)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
=== Authenticate using `client_secret_jwt`
|
||||
@@ -105,8 +108,10 @@ spring:
|
||||
|
||||
The following example shows how to configure `WebClientReactiveClientCredentialsTokenResponseClient`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
|
||||
@@ -127,7 +132,8 @@ tokenResponseClient.addParametersConverter(
|
||||
new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val jwkResolver = Function<ClientRegistration, JWK?> { clientRegistration: ClientRegistration ->
|
||||
@@ -148,14 +154,16 @@ tokenResponseClient.addParametersConverter(
|
||||
NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
|
||||
)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Customizing the JWT assertion
|
||||
|
||||
The JWT produced by `NimbusJwtClientAuthenticationParametersConverter` contains the `iss`, `sub`, `aud`, `jti`, `iat` and `exp` claims by default. You can customize the headers and/or claims by providing a `Consumer<NimbusJwtClientAuthenticationParametersConverter.JwtClientAuthenticationContext<T>>` to `setJwtClientAssertionCustomizer()`. The following example shows how to customize claims of the JWT:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Function<ClientRegistration, JWK> jwkResolver = ...
|
||||
@@ -168,7 +176,8 @@ converter.setJwtClientAssertionCustomizer((context) -> {
|
||||
});
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val jwkResolver = ...
|
||||
@@ -180,4 +189,4 @@ converter.setJwtClientAssertionCustomizer { context ->
|
||||
context.claims.claim("custom-claim", "claim-value")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
@@ -69,20 +69,23 @@ A `ClientRegistration` can be initially configured using discovery of an OpenID
|
||||
|
||||
`ClientRegistrations` provides convenience methods for configuring a `ClientRegistration` in this way, as can be seen in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
ClientRegistration clientRegistration =
|
||||
ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val clientRegistration = ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The above code will query in series `https://idp.example.com/issuer/.well-known/openid-configuration`, and then `https://idp.example.com/.well-known/openid-configuration/issuer`, and finally `https://idp.example.com/.well-known/oauth-authorization-server/issuer`, stopping at the first to return a 200 response.
|
||||
|
||||
@@ -106,8 +109,10 @@ The auto-configuration also registers the `ReactiveClientRegistrationRepository`
|
||||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
@@ -125,7 +130,8 @@ public class OAuth2ClientController {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
@@ -142,7 +148,7 @@ class OAuth2ClientController {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2Client-authorized-client]]
|
||||
== OAuth2AuthorizedClient
|
||||
@@ -163,8 +169,10 @@ From a developer perspective, the `ServerOAuth2AuthorizedClientRepository` or `R
|
||||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
@@ -183,7 +191,8 @@ public class OAuth2ClientController {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
@@ -201,7 +210,7 @@ class OAuth2ClientController {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Spring Boot 2.x auto-configuration registers an `ServerOAuth2AuthorizedClientRepository` and/or `ReactiveOAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`.
|
||||
@@ -235,8 +244,10 @@ The `ReactiveOAuth2AuthorizedClientProviderBuilder` may be used to configure and
|
||||
|
||||
The following code shows an example of how to configure and build a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -261,7 +272,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -280,7 +292,7 @@ fun authorizedClientManager(
|
||||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
When an authorization attempt succeeds, the `DefaultReactiveOAuth2AuthorizedClientManager` will delegate to the `ReactiveOAuth2AuthorizationSuccessHandler`, which (by default) will save the `OAuth2AuthorizedClient` via the `ServerOAuth2AuthorizedClientRepository`.
|
||||
In the case of a re-authorization failure, eg. a refresh token is no longer valid, the previously saved `OAuth2AuthorizedClient` will be removed from the `ServerOAuth2AuthorizedClientRepository` via the `RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler`.
|
||||
@@ -291,8 +303,10 @@ This can be useful when you need to supply a `ReactiveOAuth2AuthorizedClientProv
|
||||
|
||||
The following code shows an example of the `contextAttributesMapper`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -337,7 +351,8 @@ private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttri
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -376,7 +391,7 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<Mut
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The `DefaultReactiveOAuth2AuthorizedClientManager` is designed to be used *_within_* the context of a `ServerWebExchange`.
|
||||
When operating *_outside_* of a `ServerWebExchange` context, use `AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager` instead.
|
||||
@@ -387,8 +402,10 @@ An OAuth 2.0 Client configured with the `client_credentials` grant type can be c
|
||||
|
||||
The following code shows an example of how to configure an `AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager` that provides support for the `client_credentials` grant type:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -410,7 +427,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -426,4 +444,4 @@ fun authorizedClientManager(
|
||||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
@@ -24,8 +24,10 @@ The `ServerHttpSecurity.oauth2Client()` DSL provides a number of configuration o
|
||||
The following code shows the complete configuration options provided by the `ServerHttpSecurity.oauth2Client()` DSL:
|
||||
|
||||
.OAuth2 Client Configuration Options
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -48,7 +50,8 @@ public class OAuth2ClientSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -71,14 +74,16 @@ class OAuth2ClientSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The `ReactiveOAuth2AuthorizedClientManager` is responsible for managing the authorization (or re-authorization) of an OAuth 2.0 Client, in collaboration with one or more `ReactiveOAuth2AuthorizedClientProvider`(s).
|
||||
|
||||
The following code shows an example of how to register a `ReactiveOAuth2AuthorizedClientManager` `@Bean` and associate it with a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -103,7 +108,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -122,4 +128,4 @@ fun authorizedClientManager(
|
||||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
@@ -23,8 +23,10 @@ These claims are normally represented by a JSON object that contains a collectio
|
||||
The following code shows the complete configuration options available for the `oauth2Login()` DSL:
|
||||
|
||||
.OAuth2 Login Configuration Options
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -53,7 +55,8 @@ public class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -82,7 +85,7 @@ class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following sections go into more detail on each of the configuration options available:
|
||||
|
||||
@@ -119,8 +122,10 @@ To override the default login page, configure the `exceptionHandling().authentic
|
||||
The following listing shows an example:
|
||||
|
||||
.OAuth2 Login Page Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -153,7 +158,8 @@ public class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -187,7 +193,7 @@ class OAuth2LoginSecurityConfig {
|
||||
...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[IMPORTANT]
|
||||
You need to provide a `@Controller` with a `@RequestMapping("/login/oauth2")` that is capable of rendering the custom login page.
|
||||
@@ -220,8 +226,10 @@ The default Authorization Response redirection endpoint is `+/login/oauth2/code/
|
||||
If you would like to customize the Authorization Response redirection endpoint, configure it as shown in the following example:
|
||||
|
||||
.Redirection Endpoint Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -240,7 +248,8 @@ public class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -259,7 +268,7 @@ class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
@@ -267,7 +276,10 @@ You also need to ensure the `ClientRegistration.redirectUri` matches the custom
|
||||
|
||||
The following listing shows an example:
|
||||
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",subs="-attributes"]
|
||||
----
|
||||
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
||||
@@ -277,7 +289,8 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
||||
.build();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",subs="-attributes"]
|
||||
----
|
||||
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
||||
@@ -286,6 +299,7 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
||||
.redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
|
||||
.build()
|
||||
----
|
||||
======
|
||||
====
|
||||
|
||||
|
||||
@@ -322,8 +336,10 @@ The `GrantedAuthoritiesMapper` is given a list of granted authorities which cont
|
||||
Register a `GrantedAuthoritiesMapper` `@Bean` to have it automatically applied to the configuration, as shown in the following example:
|
||||
|
||||
.Granted Authorities Mapper Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -371,7 +387,8 @@ public class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -408,7 +425,7 @@ class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2-login-advanced-map-authorities-reactiveoauth2userservice]]
|
||||
==== Delegation-based strategy with ReactiveOAuth2UserService
|
||||
@@ -420,8 +437,10 @@ The `OAuth2UserRequest` (and `OidcUserRequest`) provides you access to the assoc
|
||||
The following example shows how to implement and configure a delegation-based strategy using an OpenID Connect 1.0 UserService:
|
||||
|
||||
.ReactiveOAuth2UserService Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -462,7 +481,8 @@ public class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -501,7 +521,7 @@ class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-advanced-oauth2-user-service]]
|
||||
@@ -518,8 +538,10 @@ If you need to customize the pre-processing of the UserInfo Request and/or the p
|
||||
|
||||
Whether you customize `DefaultReactiveOAuth2UserService` or provide your own implementation of `ReactiveOAuth2UserService`, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -542,7 +564,8 @@ public class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -564,7 +587,7 @@ class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-advanced-oidc-user-service]]
|
||||
@@ -578,8 +601,10 @@ If you need to customize the pre-processing of the UserInfo Request and/or the p
|
||||
|
||||
Whether you customize `OidcReactiveOAuth2UserService` or provide your own implementation of `ReactiveOAuth2UserService` for OpenID Connect 1.0 Provider's, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -602,7 +627,8 @@ public class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -624,7 +650,7 @@ class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-advanced-idtoken-verify]]
|
||||
@@ -641,8 +667,10 @@ The JWS algorithm resolver is a `Function` that accepts a `ClientRegistration` a
|
||||
|
||||
The following code shows how to configure the `OidcIdTokenDecoderFactory` `@Bean` to default to `MacAlgorithm.HS256` for all `ClientRegistration`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -653,7 +681,8 @@ public ReactiveJwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -663,7 +692,7 @@ fun idTokenDecoderFactory(): ReactiveJwtDecoderFactory<ClientRegistration> {
|
||||
return idTokenDecoderFactory
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
For MAC based algorithms such as `HS256`, `HS384` or `HS512`, the `client-secret` corresponding to the `client-id` is used as the symmetric key for signature verification.
|
||||
@@ -699,8 +728,10 @@ spring:
|
||||
|
||||
...and the `OidcClientInitiatedServerLogoutSuccessHandler`, which implements RP-Initiated Logout, may be configured as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -737,7 +768,8 @@ public class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -772,7 +804,7 @@ class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
NOTE: `OidcClientInitiatedServerLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
|
||||
If used, the application's base URL, like `https://app.example.org`, will replace it at request time.
|
||||
|
||||
@@ -252,8 +252,10 @@ If you need to override the auto-configuration based on your specific requiremen
|
||||
|
||||
The following example shows how to register a `ReactiveClientRegistrationRepository` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -283,7 +285,8 @@ public class OAuth2LoginConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -312,7 +315,7 @@ class OAuth2LoginConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-register-securitywebfilterchain-bean]]
|
||||
@@ -321,8 +324,10 @@ class OAuth2LoginConfig {
|
||||
The following example shows how to register a `SecurityWebFilterChain` `@Bean` with `@EnableWebFluxSecurity` and enable OAuth 2.0 login through `serverHttpSecurity.oauth2Login()`:
|
||||
|
||||
.OAuth2 Login Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -342,7 +347,8 @@ public class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -362,7 +368,7 @@ class OAuth2LoginSecurityConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-completely-override-autoconfiguration]]
|
||||
@@ -371,8 +377,10 @@ class OAuth2LoginSecurityConfig {
|
||||
The following example shows how to completely override the auto-configuration by registering a `ReactiveClientRegistrationRepository` `@Bean` and a `SecurityWebFilterChain` `@Bean`.
|
||||
|
||||
.Overriding the auto-configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -414,7 +422,8 @@ public class OAuth2LoginConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -456,7 +465,7 @@ class OAuth2LoginConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-javaconfig-wo-boot]]
|
||||
@@ -465,8 +474,10 @@ class OAuth2LoginConfig {
|
||||
If you are not able to use Spring Boot 2.x and would like to configure one of the pre-defined providers in `CommonOAuth2Provider` (for example, Google), apply the following configuration:
|
||||
|
||||
.OAuth2 Login Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -510,7 +521,8 @@ public class OAuth2LoginConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -556,4 +568,4 @@ class OAuth2LoginConfig {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
@@ -10,8 +10,10 @@ For example, you may have a need to read the bearer token from a custom header.
|
||||
To do so, you can wire an instance of `ServerBearerTokenAuthenticationConverter` into the DSL:
|
||||
|
||||
.Custom Bearer Token Header
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
ServerBearerTokenAuthenticationConverter converter = new ServerBearerTokenAuthenticationConverter();
|
||||
@@ -22,7 +24,8 @@ http
|
||||
);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val converter = ServerBearerTokenAuthenticationConverter()
|
||||
@@ -33,15 +36,17 @@ return http {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
== Bearer Token Propagation
|
||||
|
||||
Now that you have a bearer token, you can pass that to downstream services.
|
||||
This is possible with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServerBearerExchangeFilterFunction.html[ServerBearerExchangeFilterFunction]`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -52,7 +57,8 @@ public WebClient rest() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -62,13 +68,15 @@ fun rest(): WebClient {
|
||||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
When the `WebClient` shown in the preceding example performs requests, Spring Security looks up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential.
|
||||
Then, it propagates that token in the `Authorization` header -- for example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
this.rest.get()
|
||||
@@ -77,7 +85,8 @@ this.rest.get()
|
||||
.bodyToMono(String.class)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
this.rest.get()
|
||||
@@ -85,14 +94,16 @@ this.rest.get()
|
||||
.retrieve()
|
||||
.bodyToMono<String>()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The prececing example invokes the `https://other-service.example.com/endpoint`, adding the bearer token `Authorization` header for you.
|
||||
|
||||
In places where you need to override this behavior, you can supply the header yourself:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
this.rest.get()
|
||||
@@ -102,7 +113,8 @@ this.rest.get()
|
||||
.bodyToMono(String.class)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
rest.get()
|
||||
@@ -111,7 +123,7 @@ rest.get()
|
||||
.retrieve()
|
||||
.bodyToMono<String>()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In this case, the filter falls back and forwards the request onto the rest of the web filter chain.
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ First, include the needed dependencies. Second, indicate the location of the aut
|
||||
|
||||
In a Spring Boot application, you need to specify which authorization server to use:
|
||||
|
||||
====
|
||||
[source,yml]
|
||||
----
|
||||
spring:
|
||||
@@ -26,7 +25,6 @@ spring:
|
||||
jwt:
|
||||
issuer-uri: https://idp.example.com/issuer
|
||||
----
|
||||
====
|
||||
|
||||
Where `https://idp.example.com/issuer` is the value contained in the `iss` claim for JWT tokens that the authorization server issues.
|
||||
This resource server uses this property to further self-configure, discover the authorization server's public keys, and subsequently validate incoming JWTs.
|
||||
@@ -58,13 +56,11 @@ If the authorization server is down when Resource Server queries it (given appro
|
||||
|
||||
Once the application is started up, Resource Server tries to process any request that contains an `Authorization: Bearer` header:
|
||||
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
GET / HTTP/1.1
|
||||
Authorization: Bearer some-token-value # Resource Server will process this
|
||||
----
|
||||
====
|
||||
|
||||
So long as this scheme is indicated, Resource Server tries to process the request according to the Bearer Token specification.
|
||||
|
||||
@@ -91,7 +87,6 @@ From here, consider jumping to:
|
||||
|
||||
If the authorization server does not support any configuration endpoints, or if Resource Server must be able to start up independently from the authorization server, you can supply `jwk-set-uri` as well:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
@@ -102,7 +97,6 @@ spring:
|
||||
issuer-uri: https://idp.example.com
|
||||
jwk-set-uri: https://idp.example.com/.well-known/jwks.json
|
||||
----
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -125,8 +119,10 @@ Spring Boot generates two `@Bean` objects on Resource Server's behalf.
|
||||
The first bean is a `SecurityWebFilterChain` that configures the application as a resource server. When including `spring-security-oauth2-jose`, this `SecurityWebFilterChain` looks like:
|
||||
|
||||
.Resource Server SecurityWebFilterChain
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -140,7 +136,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -155,15 +152,17 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If the application does not expose a `SecurityWebFilterChain` bean, Spring Boot exposes the default one (shown in the preceding listing).
|
||||
|
||||
To replace it, expose the `@Bean` within the application:
|
||||
|
||||
.Replacing SecurityWebFilterChain
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -180,7 +179,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -196,7 +196,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The preceding configuration requires the scope of `message:read` for any URL that starts with `/messages/`.
|
||||
|
||||
@@ -205,8 +205,10 @@ Methods on the `oauth2ResourceServer` DSL also override or replace auto configur
|
||||
For example, the second `@Bean` Spring Boot creates is a `ReactiveJwtDecoder`, which decodes `String` tokens into validated instances of `Jwt`:
|
||||
|
||||
.ReactiveJwtDecoder
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -215,7 +217,8 @@ public ReactiveJwtDecoder jwtDecoder() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -223,7 +226,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
return ReactiveJwtDecoders.fromIssuerLocation(issuerUri)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -238,8 +241,10 @@ Its configuration can be overridden by using `jwkSetUri()` or replaced by using
|
||||
|
||||
You can configure an authorization server's JWK Set URI <<webflux-oauth2resourceserver-jwt-jwkseturi,as a configuration property>> or supply it in the DSL:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -257,7 +262,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -274,7 +280,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Using `jwkSetUri()` takes precedence over any configuration property.
|
||||
|
||||
@@ -283,8 +289,10 @@ Using `jwkSetUri()` takes precedence over any configuration property.
|
||||
|
||||
`decoder()` is more powerful than `jwkSetUri()`, because it completely replaces any Spring Boot auto-configuration of `JwtDecoder`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -302,7 +310,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -319,7 +328,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This is handy when you need deeper configuration, such as <<webflux-oauth2resourceserver-jwt-validation,validation>>.
|
||||
|
||||
@@ -328,8 +337,10 @@ This is handy when you need deeper configuration, such as <<webflux-oauth2resour
|
||||
|
||||
Alternately, exposing a `ReactiveJwtDecoder` `@Bean` has the same effect as `decoder()`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -338,7 +349,8 @@ public ReactiveJwtDecoder jwtDecoder() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -346,7 +358,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
return ReactiveJwtDecoders.fromIssuerLocation(issuerUri)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-decoder-algorithm]]
|
||||
== Configuring Trusted Algorithms
|
||||
@@ -360,7 +372,6 @@ You can customize this behavior with <<webflux-oauth2resourceserver-jwt-boot-alg
|
||||
|
||||
The simplest way to set the algorithm is as a property:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
@@ -371,15 +382,16 @@ spring:
|
||||
jws-algorithm: RS512
|
||||
jwk-set-uri: https://idp.example.org/.well-known/jwks.json
|
||||
----
|
||||
====
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-decoder-builder]]
|
||||
=== Customizing Trusted Algorithms by Using a Builder
|
||||
|
||||
For greater power, though, we can use a builder that ships with `NimbusReactiveJwtDecoder`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -389,7 +401,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -398,12 +411,14 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
.jwsAlgorithm(RS512).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Calling `jwsAlgorithm` more than once configures `NimbusReactiveJwtDecoder` to trust more than one algorithm:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -413,7 +428,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -422,12 +438,14 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Alternately, you can call `jwsAlgorithms`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -440,7 +458,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -453,7 +472,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-decoder-public-key]]
|
||||
=== Trusting a Single Asymmetric Key
|
||||
@@ -466,7 +485,6 @@ The public key can be provided with <<webflux-oauth2resourceserver-jwt-decoder-p
|
||||
|
||||
You can specify a key with Spring Boot:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
@@ -476,13 +494,14 @@ spring:
|
||||
jwt:
|
||||
public-key-location: classpath:my-key.pub
|
||||
----
|
||||
====
|
||||
|
||||
Alternately, to allow for a more sophisticated lookup, you can post-process the `RsaKeyConversionServicePostProcessor`:
|
||||
|
||||
.BeanFactoryPostProcessor
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -493,7 +512,8 @@ BeanFactoryPostProcessor conversionServiceCustomizer() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -504,42 +524,45 @@ fun conversionServiceCustomizer(): BeanFactoryPostProcessor {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Specify your key's location:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
key.location: hfds://my-key.pub
|
||||
----
|
||||
====
|
||||
|
||||
Then autowire the value:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Value("${key.location}")
|
||||
RSAPublicKey key;
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Value("\${key.location}")
|
||||
val key: RSAPublicKey? = null
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-decoder-public-key-builder]]
|
||||
==== Using a Builder
|
||||
|
||||
To wire an `RSAPublicKey` directly, use the appropriate `NimbusReactiveJwtDecoder` builder:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -548,7 +571,8 @@ public ReactiveJwtDecoder jwtDecoder() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -556,7 +580,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
return NimbusReactiveJwtDecoder.withPublicKey(key).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-decoder-secret-key]]
|
||||
=== Trusting a Single Symmetric Key
|
||||
@@ -564,8 +588,10 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
You can also use a single symmetric key.
|
||||
You can load in your `SecretKey` and use the appropriate `NimbusReactiveJwtDecoder` builder:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -574,7 +600,8 @@ public ReactiveJwtDecoder jwtDecoder() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -582,26 +609,26 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
return NimbusReactiveJwtDecoder.withSecretKey(this.key).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-authorization]]
|
||||
=== Configuring Authorization
|
||||
|
||||
A JWT that is issued from an OAuth 2.0 Authorization Server typically has either a `scope` or an `scp` attribute, indicating the scopes (or authorities) it has been granted -- for example:
|
||||
|
||||
====
|
||||
[source,json]
|
||||
----
|
||||
{ ..., "scope" : "messages contacts"}
|
||||
----
|
||||
====
|
||||
|
||||
When this is the case, Resource Server tries to coerce these scopes into a list of granted authorities, prefixing each scope with the string, `SCOPE_`.
|
||||
|
||||
This means that, to protect an endpoint or method with a scope derived from a JWT, the corresponding expressions should include this prefix:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -617,7 +644,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -634,25 +662,28 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can do something similar with method security:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
public Flux<Message> getMessages(...) {}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
fun getMessages(): Flux<Message> { }
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-authorization-extraction]]
|
||||
==== Extracting Authorities Manually
|
||||
@@ -663,8 +694,10 @@ At other times, the resource server may need to adapt the attribute or a composi
|
||||
|
||||
To this end, the DSL exposes `jwtAuthenticationConverter()`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -690,7 +723,8 @@ Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor()
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -713,15 +747,17 @@ fun grantedAuthoritiesExtractor(): Converter<Jwt, Mono<AbstractAuthenticationTok
|
||||
return ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
`jwtAuthenticationConverter()` is responsible for converting a `Jwt` into an `Authentication`.
|
||||
As part of its configuration, we can supply a subsidiary converter to go from `Jwt` to a `Collection` of granted authorities.
|
||||
|
||||
That final converter might be something like the following `GrantedAuthoritiesExtractor`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
static class GrantedAuthoritiesExtractor
|
||||
@@ -739,7 +775,8 @@ static class GrantedAuthoritiesExtractor
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
internal class GrantedAuthoritiesExtractor : Converter<Jwt, Collection<GrantedAuthority>> {
|
||||
@@ -752,12 +789,14 @@ internal class GrantedAuthoritiesExtractor : Converter<Jwt, Collection<GrantedAu
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
For more flexibility, the DSL supports entirely replacing the converter with any class that implements `Converter<Jwt, Mono<AbstractAuthenticationToken>>`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
static class CustomAuthenticationConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
|
||||
@@ -767,7 +806,8 @@ static class CustomAuthenticationConverter implements Converter<Jwt, Mono<Abstra
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
internal class CustomAuthenticationConverter : Converter<Jwt, Mono<AbstractAuthenticationToken>> {
|
||||
@@ -776,7 +816,7 @@ internal class CustomAuthenticationConverter : Converter<Jwt, Mono<AbstractAuthe
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-validation]]
|
||||
=== Configuring Validation
|
||||
@@ -795,8 +835,10 @@ This can cause some implementation heartburn, as the number of collaborating ser
|
||||
|
||||
Resource Server uses `JwtTimestampValidator` to verify a token's validity window, and you can configure it with a `clockSkew` to alleviate the clock drift problem:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -814,7 +856,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -827,7 +870,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -839,8 +882,10 @@ By default, Resource Server configures a clock skew of 60 seconds.
|
||||
|
||||
You can Add a check for the `aud` claim with the `OAuth2TokenValidator` API:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
|
||||
@@ -856,7 +901,8 @@ public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class AudienceValidator : OAuth2TokenValidator<Jwt> {
|
||||
@@ -870,12 +916,14 @@ class AudienceValidator : OAuth2TokenValidator<Jwt> {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Then, to add into a resource server, you can specifying the `ReactiveJwtDecoder` instance:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -893,7 +941,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -906,4 +955,4 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
@@ -17,8 +17,10 @@ In each case, two things need to be done and trade-offs are associated with how
|
||||
|
||||
One way to differentiate tenants is by the issuer claim. Since the issuer claim accompanies signed JWTs, you can do so with the `JwtIssuerReactiveAuthenticationManagerResolver`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver
|
||||
@@ -33,7 +35,8 @@ http
|
||||
);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")
|
||||
@@ -47,7 +50,7 @@ return http {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This is nice because the issuer endpoints are loaded lazily.
|
||||
In fact, the corresponding `JwtReactiveAuthenticationManager` is instantiated only when the first request with the corresponding issuer is sent.
|
||||
@@ -58,8 +61,10 @@ This allows for an application startup that is independent from those authorizat
|
||||
You may not want to restart the application each time a new tenant is added.
|
||||
In this case, you can configure the `JwtIssuerReactiveAuthenticationManagerResolver` with a repository of `ReactiveAuthenticationManager` instances, which you can edit at runtime:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
private Mono<ReactiveAuthenticationManager> addManager(
|
||||
@@ -85,7 +90,8 @@ http
|
||||
);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
private fun addManager(
|
||||
@@ -108,7 +114,7 @@ return http {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In this case, you construct `JwtIssuerReactiveAuthenticationManagerResolver` with a strategy for obtaining the `ReactiveAuthenticationManager` given to the issuer.
|
||||
This approach lets us add and remove elements from the repository (shown as a `Map` in the preceding snippet) at runtime.
|
||||
|
||||
@@ -23,7 +23,6 @@ When using https://spring.io/projects/spring-boot[Spring Boot], configuring an a
|
||||
|
||||
You can specify where the introspection endpoint is:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
@@ -35,7 +34,6 @@ spring:
|
||||
client-id: client
|
||||
client-secret: secret
|
||||
----
|
||||
====
|
||||
|
||||
Where `https://idp.example.com/introspect` is the introspection endpoint hosted by your authorization server and `client-id` and `client-secret` are the credentials needed to hit that endpoint.
|
||||
|
||||
@@ -56,13 +54,11 @@ This startup process is quite a bit simpler than for JWTs, since no endpoints ne
|
||||
|
||||
Once the application has started, Resource Server tries to process any request containing an `Authorization: Bearer` header:
|
||||
|
||||
====
|
||||
[source,http]
|
||||
----
|
||||
GET / HTTP/1.1
|
||||
Authorization: Bearer some-token-value # Resource Server will process this
|
||||
----
|
||||
====
|
||||
|
||||
So long as this scheme is indicated, Resource Server tries to process the request according to the Bearer Token specification.
|
||||
|
||||
@@ -87,8 +83,10 @@ Once a token is authenticated, an instance of `BearerTokenAuthentication` is set
|
||||
|
||||
This means that it is available in `@Controller` methods when you use `@EnableWebFlux` in your configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
@@ -97,7 +95,8 @@ public Mono<String> foo(BearerTokenAuthentication authentication) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
@@ -105,12 +104,14 @@ fun foo(authentication: BearerTokenAuthentication): Mono<String> {
|
||||
return Mono.just(authentication.tokenAttributes["sub"].toString() + " is the subject")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Since `BearerTokenAuthentication` holds an `OAuth2AuthenticatedPrincipal`, that also means that it's available to controller methods, too:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
@@ -119,7 +120,8 @@ public Mono<String> foo(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal pr
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
@@ -127,7 +129,7 @@ fun foo(@AuthenticationPrincipal principal: OAuth2AuthenticatedPrincipal): Mono<
|
||||
return Mono.just(principal.getAttribute<Any>("sub").toString() + " is the subject")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Looking Up Attributes with SpEL
|
||||
|
||||
@@ -135,8 +137,10 @@ You can access attributes with the Spring Expression Language (SpEL).
|
||||
|
||||
For example, if you use `@EnableReactiveMethodSecurity` so that you can use `@PreAuthorize` annotations, you can do:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PreAuthorize("principal?.attributes['sub'] = 'foo'")
|
||||
@@ -145,7 +149,8 @@ public Mono<String> forFoosEyesOnly() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreAuthorize("principal.attributes['sub'] = 'foo'")
|
||||
@@ -153,7 +158,7 @@ fun forFoosEyesOnly(): Mono<String> {
|
||||
return Mono.just("foo")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-opaque-sansboot]]
|
||||
== Overriding or Replacing Boot Auto Configuration
|
||||
@@ -163,8 +168,10 @@ Spring Boot generates two `@Bean` instances for Resource Server.
|
||||
The first is a `SecurityWebFilterChain` that configures the application as a resource server.
|
||||
When you use an Opaque Token, this `SecurityWebFilterChain` looks like:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -178,7 +185,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -193,15 +201,17 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If the application does not expose a `SecurityWebFilterChain` bean, Spring Boot exposes the default bean (shown in the preceding listing).
|
||||
|
||||
You can replace it by exposing the bean within the application:
|
||||
|
||||
.Replacing SecurityWebFilterChain
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -224,7 +234,8 @@ public class MyCustomSecurityConfiguration {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -242,7 +253,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The preceding example requires the scope of `message:read` for any URL that starts with `/messages/`.
|
||||
|
||||
@@ -250,8 +261,10 @@ Methods on the `oauth2ResourceServer` DSL also override or replace auto configur
|
||||
|
||||
For example, the second `@Bean` Spring Boot creates is a `ReactiveOpaqueTokenIntrospector`, which decodes `String` tokens into validated instances of `OAuth2AuthenticatedPrincipal`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -260,7 +273,8 @@ public ReactiveOpaqueTokenIntrospector introspector() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -268,7 +282,7 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
|
||||
return NimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If the application does not expose a `ReactiveOpaqueTokenIntrospector` bean, Spring Boot exposes the default one (shown in the preceding listing).
|
||||
|
||||
@@ -279,8 +293,10 @@ You can override its configuration by using `introspectionUri()` and `introspect
|
||||
|
||||
You can configure an authorization server's Introspection URI <<webflux-oauth2resourceserver-opaque-introspectionuri,as a configuration property>>, or you can supply in the DSL:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -303,7 +319,8 @@ public class DirectlyConfiguredIntrospectionUri {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -321,7 +338,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Using `introspectionUri()` takes precedence over any configuration property.
|
||||
|
||||
@@ -330,8 +347,10 @@ Using `introspectionUri()` takes precedence over any configuration property.
|
||||
|
||||
`introspector()` is more powerful than `introspectionUri()`. It completely replaces any Boot auto-configuration of `ReactiveOpaqueTokenIntrospector`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -353,7 +372,8 @@ public class DirectlyConfiguredIntrospector {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -370,7 +390,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This is handy when deeper configuration, such as <<webflux-oauth2resourceserver-opaque-authorization-extraction,authority mapping>>or <<webflux-oauth2resourceserver-opaque-jwt-introspector,JWT revocation>>, is necessary.
|
||||
|
||||
@@ -379,8 +399,10 @@ This is handy when deeper configuration, such as <<webflux-oauth2resourceserver-
|
||||
|
||||
Or, exposing a `ReactiveOpaqueTokenIntrospector` `@Bean` has the same effect as `introspector()`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -389,7 +411,8 @@ public ReactiveOpaqueTokenIntrospector introspector() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -397,26 +420,26 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
|
||||
return NimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-opaque-authorization]]
|
||||
== Configuring Authorization
|
||||
|
||||
An OAuth 2.0 Introspection endpoint typically returns a `scope` attribute, indicating the scopes (or authorities) it has been granted -- for example:
|
||||
|
||||
====
|
||||
[source,json]
|
||||
----
|
||||
{ ..., "scope" : "messages contacts"}
|
||||
----
|
||||
====
|
||||
|
||||
When this is the case, Resource Server tries to coerce these scopes into a list of granted authorities, prefixing each scope with a string: `SCOPE_`.
|
||||
|
||||
This means that, to protect an endpoint or method with a scope derived from an Opaque Token, the corresponding expressions should include this prefix:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -436,7 +459,8 @@ public class MappedAuthorities {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -453,25 +477,28 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can do something similar with method security:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
public Flux<Message> getMessages(...) {}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
fun getMessages(): Flux<Message> { }
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-opaque-authorization-extraction]]
|
||||
=== Extracting Authorities Manually
|
||||
@@ -492,8 +519,10 @@ If the introspection response were as the preceding example shows, Resource Serv
|
||||
|
||||
You can customize behavior by using a custom `ReactiveOpaqueTokenIntrospector` that looks at the attribute set and converts in its own way:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class CustomAuthoritiesOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
|
||||
@@ -515,7 +544,8 @@ public class CustomAuthoritiesOpaqueTokenIntrospector implements ReactiveOpaqueT
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class CustomAuthoritiesOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
@@ -535,12 +565,14 @@ class CustomAuthoritiesOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Thereafter, you can configure this custom introspector by exposing it as a `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -549,7 +581,8 @@ public ReactiveOpaqueTokenIntrospector introspector() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -557,7 +590,7 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
|
||||
return CustomAuthoritiesOpaqueTokenIntrospector()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-opaque-jwt-introspector]]
|
||||
== Using Introspection with JWTs
|
||||
@@ -569,7 +602,6 @@ So, suppose you need to check with the authorization server on each request, in
|
||||
|
||||
Even though you are using the JWT format for the token, your validation method is introspection, meaning you would want to do:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
@@ -581,7 +613,6 @@ spring:
|
||||
client-id: client
|
||||
client-secret: secret
|
||||
----
|
||||
====
|
||||
|
||||
In this case, the resulting `Authentication` would be `BearerTokenAuthentication`.
|
||||
Any attributes in the corresponding `OAuth2AuthenticatedPrincipal` would be whatever was returned by the introspection endpoint.
|
||||
@@ -591,8 +622,10 @@ Now what?
|
||||
|
||||
In this case, you can create a custom `ReactiveOpaqueTokenIntrospector` that still hits the endpoint but then updates the returned principal to have the JWTs claims as the attributes:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class JwtOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
|
||||
@@ -618,7 +651,8 @@ public class JwtOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospect
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class JwtOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
@@ -641,12 +675,14 @@ class JwtOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Thereafter, you can configure this custom introspector by exposing it as a `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -655,7 +691,8 @@ public ReactiveOpaqueTokenIntrospector introspector() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -663,7 +700,7 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
|
||||
return JwtOpaqueTokenIntrospector()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-opaque-userinfo]]
|
||||
== Calling a `/userinfo` Endpoint
|
||||
@@ -679,8 +716,10 @@ The implementation in the next listing does three things:
|
||||
* Looks up the appropriate client registration associated with the `/userinfo` endpoint.
|
||||
* Invokes and returns the response from the `/userinfo` endpoint.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
|
||||
@@ -709,7 +748,8 @@ public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntro
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
@@ -732,13 +772,15 @@ class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If you aren't using `spring-security-oauth2-client`, it's still quite simple.
|
||||
You will simply need to invoke the `/userinfo` with your own instance of `WebClient`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
|
||||
@@ -754,7 +796,8 @@ public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntro
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
@@ -767,12 +810,14 @@ class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Either way, having created your `ReactiveOpaqueTokenIntrospector`, you should publish it as a `@Bean` to override the defaults:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -781,7 +826,8 @@ ReactiveOpaqueTokenIntrospector introspector() {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -789,4 +835,4 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
|
||||
return UserInfoOpaqueTokenIntrospector()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
For example, we can test our example from xref:reactive/authorization/method.adoc#jc-erms[EnableReactiveMethodSecurity] by using the same setup and annotations that we used in xref:servlet/test/method.adoc#test-method[Testing Method Security].
|
||||
The following minimal sample shows what we can do:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@@ -39,7 +41,8 @@ public class HelloWorldMessageServiceTests {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@@ -72,4 +75,4 @@ class HelloWorldMessageServiceTests {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
After xref:reactive/test/web/setup.adoc[applying the Spring Security support to `WebTestClient`], we can use either annotations or `mutateWith` support -- for example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
|
||||
@@ -64,7 +66,8 @@ public void messageWhenMutateWithMockAdminThenOk() throws Exception {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.expectBody
|
||||
@@ -111,6 +114,6 @@ fun messageWhenMutateWithMockAdminThenOk() {
|
||||
.expectBody<String>().isEqualTo("Hello World!")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In addition to `mockUser()`, Spring Security ships with several other convenience mutators for things like xref:reactive/test/web/csrf.adoc[CSRF] and xref:reactive/test/web/oauth2.adoc[OAuth 2.0].
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
Spring Security also provides support for CSRF testing with `WebTestClient` -- for example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
|
||||
@@ -16,7 +18,8 @@ this.rest
|
||||
...
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf
|
||||
@@ -28,4 +31,4 @@ this.rest
|
||||
.uri("/login")
|
||||
...
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,10 @@
|
||||
|
||||
The basic setup looks like this:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
|
||||
@@ -31,7 +33,8 @@ public class HelloWebfluxMethodApplicationTests {
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity
|
||||
@@ -58,4 +61,4 @@ class HelloWebfluxMethodApplicationTests {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Reference in New Issue
Block a user