Revert unnecessary commits from main
Issue gh-15016
This commit is contained in:
@@ -129,8 +129,6 @@
|
||||
** Authentication
|
||||
*** xref:reactive/authentication/x509.adoc[X.509 Authentication]
|
||||
*** xref:reactive/authentication/logout.adoc[Logout]
|
||||
*** Session Management
|
||||
**** xref:reactive/authentication/concurrent-sessions-control.adoc[Concurrent Sessions Control]
|
||||
** Authorization
|
||||
*** xref:reactive/authorization/authorize-http-requests.adoc[Authorize HTTP Requests]
|
||||
*** xref:reactive/authorization/method.adoc[EnableReactiveMethodSecurity]
|
||||
|
||||
@@ -591,99 +591,3 @@ http {
|
||||
======
|
||||
|
||||
With the above configuration, when a password manager navigates to `/.well-known/change-password`, then Spring Security will redirect to `/update-password`.
|
||||
|
||||
[[authentication-compromised-password-check]]
|
||||
== Compromised Password Checking
|
||||
|
||||
There are some scenarios where you need to check whether a password has been compromised, for example, if you are creating an application that deals with sensitive data, it is often needed that you perform some check on user's passwords in order to assert its reliability.
|
||||
One of these checks can be if the password has been compromised, usually because it has been found in a https://wikipedia.org/wiki/Data_breach[data breach].
|
||||
|
||||
To facilitate that, Spring Security provides integration with the https://haveibeenpwned.com/API/v3#PwnedPasswords[Have I Been Pwned API] via the {security-api-url}org/springframework/security/core/password/HaveIBeenPwnedRestApiPasswordChecker.html[`HaveIBeenPwnedRestApiPasswordChecker` implementation] of the {security-api-url}org/springframework/security/core/password/CompromisedPasswordChecker.html[`CompromisedPasswordChecker` interface].
|
||||
|
||||
You can either use the `CompromisedPasswordChecker` API by yourself or, if you are using xref:servlet/authentication/passwords/dao-authentication-provider.adoc[the `DaoAuthenticationProvider]` via xref:servlet/authentication/passwords/index.adoc[Spring Security authentication mechanisms], you can provide a `CompromisedPasswordChecker` bean, and it will be automatically picked up by Spring Security configuration.
|
||||
|
||||
.Using CompromisedPasswordChecker as a bean
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults())
|
||||
.httpBasic(withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CompromisedPasswordChecker compromisedPasswordChecker() {
|
||||
return new HaveIBeenPwnedRestApiPasswordChecker();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun filterChain(http:HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
formLogin {}
|
||||
httpBasic {}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun compromisedPasswordChecker(): CompromisedPasswordChecker {
|
||||
return HaveIBeenPwnedRestApiPasswordChecker()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
By doing that, when you try to authenticate via HTTP Basic or Form Login using a weak password, let's say `123456`, you will receive a 401 response status code.
|
||||
However, just a 401 is not so useful in that case, it will cause some confusion because the user provided the right password and still was not allowed to log in.
|
||||
In such cases, you can handle the `CompromisedPasswordException` to perform your desired logic, like redirecting the user-agent to `/reset-password`, for example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ControllerAdvice
|
||||
public class MyControllerAdvice {
|
||||
|
||||
@ExceptionHandler(CompromisedPasswordException.class)
|
||||
public String handleCompromisedPasswordException(CompromisedPasswordException ex, RedirectAttributes attributes) {
|
||||
attributes.addFlashAttribute("error", ex.message);
|
||||
return "redirect:/reset-password";
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ControllerAdvice
|
||||
class MyControllerAdvice {
|
||||
|
||||
@ExceptionHandler(CompromisedPasswordException::class)
|
||||
fun handleCompromisedPasswordException(ex: CompromisedPasswordException, attributes: RedirectAttributes): RedirectView {
|
||||
attributes.addFlashAttribute("error", ex.message)
|
||||
return RedirectView("/reset-password")
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -1,458 +0,0 @@
|
||||
[[reactive-concurrent-sessions-control]]
|
||||
= Concurrent Sessions Control
|
||||
|
||||
Similar to xref:servlet/authentication/session-management.adoc#ns-concurrent-sessions[Servlet's Concurrent Sessions Control], Spring Security also provides support to limit the number of concurrent sessions a user can have in a Reactive application.
|
||||
|
||||
When you set up Concurrent Sessions Control in Spring Security, it monitors authentications carried out through Form Login, xref:reactive/oauth2/login/index.adoc[OAuth 2.0 Login], and HTTP Basic authentication by hooking into the way those authentication mechanisms handle authentication success.
|
||||
More specifically, the session management DSL will add the {security-api-url}org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.html[ConcurrentSessionControlServerAuthenticationSuccessHandler] and the {security-api-url}org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.html[RegisterSessionServerAuthenticationSuccessHandler] to the list of `ServerAuthenticationSuccessHandler` used by the authentication filter.
|
||||
|
||||
The following sections contains examples of how to configure Concurrent Sessions Control.
|
||||
|
||||
* <<reactive-concurrent-sessions-control-limit,I want to limit the number of concurrent sessions a user can have>>
|
||||
* <<concurrent-sessions-control-custom-strategy,I want to customize the strategy used when the maximum number of sessions is exceeded>>
|
||||
* <<reactive-concurrent-sessions-control-specify-session-registry,I want to know how to specify a `ReactiveSessionRegistry`>>
|
||||
* <<concurrent-sessions-control-sample,I want to see a sample application that uses Concurrent Sessions Control>>
|
||||
* <<disabling-for-authentication-filters,I want to know how to disable it for some authentication filter>>
|
||||
|
||||
[[reactive-concurrent-sessions-control-limit]]
|
||||
== Limiting Concurrent Sessions
|
||||
|
||||
By default, Spring Security will allow any number of concurrent sessions for a user.
|
||||
To limit the number of concurrent sessions, you can use the `maximumSessions` DSL method:
|
||||
|
||||
.Configuring one session for any user
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.of(1))
|
||||
)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveSessionRegistry reactiveSessionRegistry() {
|
||||
return new InMemoryReactiveSessionRegistry();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.of(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
|
||||
return InMemoryReactiveSessionRegistry()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The above configuration allows one session for any user.
|
||||
Similarly, you can also allow unlimited sessions by using the `SessionLimit#UNLIMITED` constant:
|
||||
|
||||
.Configuring unlimited sessions
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.UNLIMITED))
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveSessionRegistry reactiveSessionRegistry() {
|
||||
return new InMemoryReactiveSessionRegistry();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.UNLIMITED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
|
||||
return InMemoryReactiveSessionRegistry()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Since the `maximumSessions` method accepts a `SessionLimit` interface, which in turn extends `Function<Authentication, Mono<Integer>>`, you can have a more complex logic to determine the maximum number of sessions based on the user's authentication:
|
||||
|
||||
.Configuring maximumSessions based on `Authentication`
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(maxSessions()))
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
private SessionLimit maxSessions() {
|
||||
return (authentication) -> {
|
||||
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) {
|
||||
return Mono.empty(); // allow unlimited sessions for users with ROLE_UNLIMITED_SESSIONS
|
||||
}
|
||||
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
|
||||
return Mono.just(2); // allow two sessions for admins
|
||||
}
|
||||
return Mono.just(1); // allow one session for every other user
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveSessionRegistry reactiveSessionRegistry() {
|
||||
return new InMemoryReactiveSessionRegistry();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = maxSessions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun maxSessions(): SessionLimit {
|
||||
return { authentication ->
|
||||
if (authentication.authorities.contains(SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) Mono.empty
|
||||
if (authentication.authorities.contains(SimpleGrantedAuthority("ROLE_ADMIN"))) Mono.just(2)
|
||||
Mono.just(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
|
||||
return InMemoryReactiveSessionRegistry()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
When the maximum number of sessions is exceeded, by default, the least recently used session(s) will be expired.
|
||||
If you want to change that behavior, you can <<concurrent-sessions-control-custom-strategy,customize the strategy used when the maximum number of sessions is exceeded>>.
|
||||
|
||||
[[concurrent-sessions-control-custom-strategy]]
|
||||
== Handling Maximum Number of Sessions Exceeded
|
||||
|
||||
By default, when the maximum number of sessions is exceeded, the least recently used session(s) will be expired by using the {security-api-url}org/springframework/security/web/server/authentication/session/InvalidateLeastUsedMaximumSessionsExceededHandler.html[InvalidateLeastUsedMaximumSessionsExceededHandler].
|
||||
Spring Security also provides another implementation that prevents the user from creating new sessions by using the {security-api-url}org/springframework/security/web/server/authentication/session/PreventLoginMaximumSessionsExceededHandler.html[PreventLoginMaximumSessionsExceededHandler].
|
||||
If you want to use your own strategy, you can provide a different implementation of {security-api-url}org/springframework/security/web/server/authentication/session/ServerMaximumSessionsExceededHandler.html[ServerMaximumSessionsExceededHandler].
|
||||
|
||||
.Configuring maximumSessionsExceededHandler
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.of(1))
|
||||
.maximumSessionsExceededHandler(new PreventLoginMaximumSessionsExceededHandler())
|
||||
)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveSessionRegistry reactiveSessionRegistry() {
|
||||
return new InMemoryReactiveSessionRegistry();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.of(1)
|
||||
maximumSessionsExceededHandler = PreventLoginMaximumSessionsExceededHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
|
||||
return InMemoryReactiveSessionRegistry()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[reactive-concurrent-sessions-control-specify-session-registry]]
|
||||
== Specifying a `ReactiveSessionRegistry`
|
||||
|
||||
In order to keep track of the user's sessions, Spring Security uses a {security-api-url}org/springframework/security/core/session/ReactiveSessionRegistry.html[ReactiveSessionRegistry], and, every time a user logs in, their session information is saved.
|
||||
|
||||
Spring Security ships with {security-api-url}org/springframework/security/core/session/InMemoryReactiveSessionRegistry.html[InMemoryReactiveSessionRegistry] implementation of `ReactiveSessionRegistry`.
|
||||
|
||||
To specify a `ReactiveSessionRegistry` implementation you can either declare it as a bean:
|
||||
|
||||
.ReactiveSessionRegistry as a Bean
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.of(1))
|
||||
)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveSessionRegistry reactiveSessionRegistry() {
|
||||
return new MyReactiveSessionRegistry();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.of(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
|
||||
return MyReactiveSessionRegistry()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
or you can use the `sessionRegistry` DSL method:
|
||||
|
||||
.ReactiveSessionRegistry using sessionRegistry DSL method
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.of(1))
|
||||
.sessionRegistry(new MyReactiveSessionRegistry())
|
||||
)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.of(1)
|
||||
sessionRegistry = MyReactiveSessionRegistry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[reactive-concurrent-sessions-control-manually-invalidating-sessions]]
|
||||
== Invalidating Registered User's Sessions
|
||||
|
||||
At times, it is handy to be able to invalidate all or some of a user's sessions.
|
||||
For example, when a user changes their password, you may want to invalidate all of their sessions so that they are forced to log in again.
|
||||
To do that, you can use the `ReactiveSessionRegistry` bean to retrieve all the user's sessions, invalidate them, and them remove them from the `WebSessionStore`:
|
||||
|
||||
.Using ReactiveSessionRegistry to invalidate sessions manually
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class SessionControl {
|
||||
private final ReactiveSessionRegistry reactiveSessionRegistry;
|
||||
|
||||
private final WebSessionStore webSessionStore;
|
||||
|
||||
public Mono<Void> invalidateSessions(String username) {
|
||||
return this.reactiveSessionRegistry.getAllSessions(username)
|
||||
.flatMap((session) -> session.invalidate().thenReturn(session))
|
||||
.flatMap((session) -> this.webSessionStore.removeSession(session.getSessionId()))
|
||||
.then();
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[disabling-for-authentication-filters]]
|
||||
== Disabling It for Some Authentication Filters
|
||||
|
||||
By default, Concurrent Sessions Control will be configured automatically for Form Login, OAuth 2.0 Login, and HTTP Basic authentication as long as they do not specify an `ServerAuthenticationSuccessHandler` themselves.
|
||||
For example, the following configuration will disable Concurrent Sessions Control for Form Login:
|
||||
|
||||
.Disabling Concurrent Sessions Control for Form Login
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin((login) -> login
|
||||
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/"))
|
||||
)
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.of(1))
|
||||
)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
formLogin {
|
||||
authenticationSuccessHandler = RedirectServerAuthenticationSuccessHandler("/")
|
||||
}
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.of(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
=== Adding Additional Success Handlers Without Disabling Concurrent Sessions Control
|
||||
|
||||
You can also include additional `ServerAuthenticationSuccessHandler` instances to the list of handlers used by the authentication filter without disabling Concurrent Sessions Control.
|
||||
To do that you can use the `authenticationSuccessHandler(Consumer<List<ServerAuthenticationSuccessHandler>>)` method:
|
||||
|
||||
.Adding additional handlers
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin((login) -> login
|
||||
.authenticationSuccessHandler((handlers) -> handlers.add(new MyAuthenticationSuccessHandler()))
|
||||
)
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.of(1))
|
||||
)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[concurrent-sessions-control-sample]]
|
||||
== Checking a Sample Application
|
||||
|
||||
You can check the {gh-samples-url}/reactive/webflux/java/session-management/maximum-sessions[sample application here].
|
||||
@@ -19,7 +19,7 @@ This improves upon `@EnableReactiveMethodSecurity` in a number of ways. `@Enable
|
||||
|
||||
1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters.
|
||||
This simplifies reuse and customization.
|
||||
2. Supports reactive return types including Kotlin coroutines.
|
||||
2. Supports reactive return types. Note that we are waiting on https://github.com/spring-projects/spring-framework/issues/22462[additional coroutine support from the Spring Framework] before adding coroutine support.
|
||||
3. Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize
|
||||
4. Checks for conflicting annotations to ensure an unambiguous security configuration
|
||||
5. Complies with JSR-250
|
||||
@@ -304,6 +304,13 @@ and it will be invoked after the `@PostAuthorize` interceptor.
|
||||
|
||||
== EnableReactiveMethodSecurity
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
`@EnableReactiveMethodSecurity` also supports Kotlin coroutines, though only to a limited degree.
|
||||
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].
|
||||
====
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
|
||||
@@ -22,7 +22,7 @@ The `OAuth2AuthorizationRequestRedirectWebFilter` uses a `ServerOAuth2Authorizat
|
||||
The primary role of the `ServerOAuth2AuthorizationRequestResolver` is to resolve an `OAuth2AuthorizationRequest` from the provided web request.
|
||||
The default implementation `DefaultServerOAuth2AuthorizationRequestResolver` matches on the (default) path `+/oauth2/authorization/{registrationId}+` extracting the `registrationId` and using it to build the `OAuth2AuthorizationRequest` for the associated `ClientRegistration`.
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml,attrs="-attributes"]
|
||||
----
|
||||
@@ -107,9 +107,7 @@ For example, OpenID Connect defines additional OAuth 2.0 request parameters for
|
||||
One of those extended parameters is the `prompt` parameter.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `prompt` parameter is optional. Space delimited, case sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for re-authentication and consent. The defined values are: `none`, `login`, `consent`, and `select_account`.
|
||||
====
|
||||
OPTIONAL. Space delimited, case sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for reauthentication and consent. The defined values are: none, login, consent, select_account
|
||||
|
||||
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`.
|
||||
|
||||
@@ -575,7 +573,7 @@ which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the
|
||||
|
||||
=== Using the Access Token
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -782,7 +780,7 @@ which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the
|
||||
|
||||
=== Using the Access Token
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -1035,7 +1033,7 @@ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
|
||||
=== Using the Access Token
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -1158,215 +1156,3 @@ class OAuth2ResourceServerController {
|
||||
|
||||
[TIP]
|
||||
If you need to resolve the `Jwt` assertion from a different source, you can provide `JwtBearerReactiveOAuth2AuthorizedClientProvider.setJwtAssertionResolver()` with a custom `Function<OAuth2AuthorizationContext, Mono<Jwt>>`.
|
||||
|
||||
[[oauth2Client-token-exchange-grant]]
|
||||
== Token Exchange
|
||||
|
||||
[NOTE]
|
||||
Please refer to OAuth 2.0 Token Exchange for further details on the https://datatracker.ietf.org/doc/html/rfc8693[Token Exchange] grant.
|
||||
|
||||
|
||||
=== Requesting an Access Token
|
||||
|
||||
[NOTE]
|
||||
Please refer to the https://datatracker.ietf.org/doc/html/rfc8693#section-2[Token Exchange Request and Response] protocol flow for the Token Exchange grant.
|
||||
|
||||
The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Token Exchange grant is `WebClientReactiveTokenExchangeTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server’s Token Endpoint.
|
||||
|
||||
The `WebClientReactiveTokenExchangeTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response.
|
||||
|
||||
|
||||
=== Customizing the Access Token Request
|
||||
|
||||
If you need to customize the pre-processing of the Token Request, you can provide `WebClientReactiveTokenExchangeTokenResponseClient.setParametersConverter()` with a custom `Converter<TokenExchangeGrantRequest, MultiValueMap<String, String>>`.
|
||||
The default implementation builds a `MultiValueMap<String, String>` containing only the `grant_type` parameter of a standard https://tools.ietf.org/html/rfc6749#section-4.4.2[OAuth 2.0 Access Token Request] which is used to construct the request.
|
||||
Other parameters required by the Token Exchange grant are added directly to the body of the request by the `WebClientReactiveTokenExchangeTokenResponseClient`.
|
||||
However, providing a custom `Converter`, would allow you to extend the standard Token Request and add custom parameter(s).
|
||||
|
||||
[TIP]
|
||||
If you prefer to only add additional parameters, you can instead provide `WebClientReactiveTokenExchangeTokenResponseClient.addParametersConverter()` with a custom `Converter<TokenExchangeGrantRequest, MultiValueMap<String, String>>` which constructs an aggregate `Converter`.
|
||||
|
||||
IMPORTANT: The custom `Converter` must return valid parameters of an OAuth 2.0 Access Token Request that is understood by the intended OAuth 2.0 Provider.
|
||||
|
||||
=== Customizing the Access Token Response
|
||||
|
||||
On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `WebClientReactiveTokenExchangeTokenResponseClient.setBodyExtractor()` with a custom configured `BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage>` that is used for converting the OAuth 2.0 Access Token Response to an `OAuth2AccessTokenResponse`.
|
||||
The default implementation provided by `OAuth2BodyExtractors.oauth2AccessTokenResponse()` parses the response and handles errors accordingly.
|
||||
|
||||
=== Customizing the `WebClient`
|
||||
|
||||
Alternatively, if your requirements are more advanced, you can take full control of the request/response by simply providing `WebClientReactiveTokenExchangeTokenResponseClient.setWebClient()` with a custom configured `WebClient`.
|
||||
|
||||
Whether you customize `WebClientReactiveTokenExchangeTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeTokenResponseClient = ...
|
||||
|
||||
TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider();
|
||||
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient);
|
||||
|
||||
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build();
|
||||
|
||||
...
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
val tokenExchangeTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> = ...
|
||||
|
||||
val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider()
|
||||
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient)
|
||||
|
||||
val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build()
|
||||
|
||||
...
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
======
|
||||
|
||||
=== Using the Access Token
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
okta:
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
|
||||
scope: read
|
||||
provider:
|
||||
okta:
|
||||
token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
|
||||
----
|
||||
|
||||
...and the `OAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
|
||||
TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
|
||||
new TokenExchangeReactiveOAuth2AuthorizedClientProvider();
|
||||
|
||||
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build();
|
||||
|
||||
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
new DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
return authorizedClientManager;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun authorizedClientManager(
|
||||
clientRegistrationRepository: ReactiveClientRegistrationRepository,
|
||||
authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
|
||||
val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider()
|
||||
val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build()
|
||||
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RestController
|
||||
public class OAuth2ResourceServerController {
|
||||
|
||||
@Autowired
|
||||
private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
|
||||
|
||||
@GetMapping("/resource")
|
||||
public Mono<String> resource(JwtAuthenticationToken jwtAuthentication) {
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(jwtAuthentication)
|
||||
.build();
|
||||
|
||||
return this.authorizedClientManager.authorize(authorizeRequest)
|
||||
.map(OAuth2AuthorizedClient::getAccessToken)
|
||||
...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class OAuth2ResourceServerController {
|
||||
|
||||
@Autowired
|
||||
private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager
|
||||
|
||||
@GetMapping("/resource")
|
||||
fun resource(jwtAuthentication: JwtAuthenticationToken): Mono<String> {
|
||||
val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(jwtAuthentication)
|
||||
.build()
|
||||
return authorizedClientManager.authorize(authorizeRequest)
|
||||
.map { it.accessToken }
|
||||
...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`TokenExchangeReactiveOAuth2AuthorizedClientProvider` resolves the subject token (as an `OAuth2Token`) via `OAuth2AuthorizationContext.getPrincipal().getPrincipal()` by default, hence the use of `JwtAuthenticationToken` in the preceding example.
|
||||
An actor token is not resolved by default.
|
||||
|
||||
[TIP]
|
||||
If you need to resolve the subject token from a different source, you can provide `TokenExchangeReactiveOAuth2AuthorizedClientProvider.setSubjectTokenResolver()` with a custom `Function<OAuth2AuthorizationContext, Mono<OAuth2Token>>`.
|
||||
|
||||
[TIP]
|
||||
If you need to resolve an actor token, you can provide `TokenExchangeReactiveOAuth2AuthorizedClientProvider.setActorTokenResolver()` with a custom `Function<OAuth2AuthorizationContext, Mono<OAuth2Token>>`.
|
||||
|
||||
@@ -18,7 +18,7 @@ is supplied by the `com.nimbusds.jose.jwk.JWK` resolver associated with `NimbusJ
|
||||
|
||||
=== Authenticate using `private_key_jwt`
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -89,7 +89,7 @@ tokenResponseClient.addParametersConverter(
|
||||
|
||||
=== Authenticate using `client_secret_jwt`
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
|
||||
@@ -59,7 +59,7 @@ The name may be used in certain scenarios, such as when displaying the name of t
|
||||
which contains the cryptographic key(s) used to verify the https://tools.ietf.org/html/rfc7515[JSON Web Signature (JWS)] of the ID Token and optionally the UserInfo Response.
|
||||
<12> `issuerUri`: Returns the issuer identifier uri for the OpenID Connect 1.0 provider or the OAuth 2.0 Authorization Server.
|
||||
<13> `configurationMetadata`: The https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Provider Configuration Information].
|
||||
This information will only be available if the Spring Boot property `spring.security.oauth2.client.provider.[providerId].issuerUri` is configured.
|
||||
This information will only be available if the Spring Boot 2.x property `spring.security.oauth2.client.provider.[providerId].issuerUri` is configured.
|
||||
<14> `(userInfoEndpoint)uri`: The UserInfo Endpoint URI used to access the claims/attributes of the authenticated end-user.
|
||||
<15> `(userInfoEndpoint)authenticationMethod`: The authentication method used when sending the access token to the UserInfo Endpoint.
|
||||
The supported values are *header*, *form* and *query*.
|
||||
@@ -100,7 +100,7 @@ The `ReactiveClientRegistrationRepository` serves as a repository for OAuth 2.0
|
||||
Client registration information is ultimately stored and owned by the associated Authorization Server.
|
||||
This repository provides the ability to retrieve a sub-set of the primary client registration information, which is stored with the Authorization Server.
|
||||
|
||||
Spring Boot auto-configuration binds each of the properties under `spring.security.oauth2.client.registration._[registrationId]_` to an instance of `ClientRegistration` and then composes each of the `ClientRegistration` instance(s) within a `ReactiveClientRegistrationRepository`.
|
||||
Spring Boot 2.x auto-configuration binds each of the properties under `spring.security.oauth2.client.registration._[registrationId]_` to an instance of `ClientRegistration` and then composes each of the `ClientRegistration` instance(s) within a `ReactiveClientRegistrationRepository`.
|
||||
|
||||
[NOTE]
|
||||
The default implementation of `ReactiveClientRegistrationRepository` is `InMemoryReactiveClientRegistrationRepository`.
|
||||
@@ -213,7 +213,7 @@ class OAuth2ClientController {
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Spring Boot auto-configuration registers an `ServerOAuth2AuthorizedClientRepository` and/or `ReactiveOAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`.
|
||||
Spring Boot 2.x auto-configuration registers an `ServerOAuth2AuthorizedClientRepository` and/or `ReactiveOAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`.
|
||||
However, the application may choose to override and register a custom `ServerOAuth2AuthorizedClientRepository` or `ReactiveOAuth2AuthorizedClientService` `@Bean`.
|
||||
|
||||
The default implementation of `ReactiveOAuth2AuthorizedClientService` is `InMemoryReactiveOAuth2AuthorizedClientService`, which stores `OAuth2AuthorizedClient`(s) in-memory.
|
||||
|
||||
@@ -12,13 +12,12 @@ At a high-level, the core features available are:
|
||||
* https://tools.ietf.org/html/rfc6749#section-1.3.4[Client Credentials]
|
||||
* https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials]
|
||||
* https://datatracker.ietf.org/doc/html/rfc7523#section-2.1[JWT Bearer]
|
||||
* https://datatracker.ietf.org/doc/html/rfc8693#section-2.1[Token Exchange]
|
||||
|
||||
.Client Authentication support
|
||||
* https://datatracker.ietf.org/doc/html/rfc7523#section-2.2[JWT Bearer]
|
||||
|
||||
.HTTP Client support
|
||||
* xref:reactive/oauth2/client/authorized-clients.adoc#oauth2Client-webclient-webflux[`WebClient` integration for Reactive Environments] (for requesting protected resources)
|
||||
* <<oauth2Client-webclient-webflux, `WebClient` integration for Reactive Environments>> (for requesting protected resources)
|
||||
|
||||
The `ServerHttpSecurity.oauth2Client()` DSL provides a number of configuration options for customizing the core components used by OAuth 2.0 Client.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -472,13 +472,7 @@ public class OAuth2LoginSecurityConfig {
|
||||
// 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
|
||||
|
||||
// 3) Create a copy of oidcUser but use the mappedAuthorities instead
|
||||
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
|
||||
String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName();
|
||||
if (StringUtils.hasText(userNameAttributeName)) {
|
||||
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo(), userNameAttributeName);
|
||||
} else {
|
||||
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
|
||||
}
|
||||
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
|
||||
|
||||
return Mono.just(oidcUser);
|
||||
});
|
||||
@@ -519,13 +513,7 @@ class OAuth2LoginSecurityConfig {
|
||||
// 1) Fetch the authority information from the protected resource using accessToken
|
||||
// 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
|
||||
// 3) Create a copy of oidcUser but use the mappedAuthorities instead
|
||||
val providerDetails = userRequest.getClientRegistration().getProviderDetails()
|
||||
val userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName()
|
||||
val mappedOidcUser = if (StringUtils.hasText(userNameAttributeName)) {
|
||||
DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo, userNameAttributeName)
|
||||
} else {
|
||||
DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
|
||||
}
|
||||
val mappedOidcUser = DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
|
||||
|
||||
Mono.just(mappedOidcUser)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
= Core Configuration
|
||||
|
||||
[[webflux-oauth2-login-sample]]
|
||||
== Spring Boot Sample
|
||||
== Spring Boot 2.x Sample
|
||||
|
||||
Spring Boot brings full auto-configuration capabilities for OAuth 2.0 Login.
|
||||
Spring Boot 2.x brings full auto-configuration capabilities for OAuth 2.0 Login.
|
||||
|
||||
This section shows how to configure the {gh-samples-url}/boot/oauth2login-webflux[*OAuth 2.0 Login WebFlux sample*] by using _Google_ as the _Authentication Provider_ and covers the following topics:
|
||||
|
||||
@@ -80,7 +80,7 @@ spring:
|
||||
[[webflux-oauth2-login-sample-start]]
|
||||
=== Boot the Application
|
||||
|
||||
Launch the Spring Boot sample and go to `http://localhost:8080`.
|
||||
Launch the Spring Boot 2.x sample and go to `http://localhost:8080`.
|
||||
You are then redirected to the default _auto-generated_ login page, which displays a link for Google.
|
||||
|
||||
Click on the Google link, and you are then redirected to Google for authentication.
|
||||
@@ -93,12 +93,12 @@ At this point, the OAuth Client retrieves your email address and basic profile i
|
||||
|
||||
|
||||
[[oauth2login-boot-property-mappings]]
|
||||
== Spring Boot Property Mappings
|
||||
== Spring Boot 2.x Property Mappings
|
||||
|
||||
The following table outlines the mapping of the Spring Boot OAuth Client properties to the xref:reactive/oauth2/client/core.adoc#oauth2Client-client-registration[ClientRegistration] properties.
|
||||
The following table outlines the mapping of the Spring Boot 2.x OAuth Client properties to the xref:reactive/oauth2/client/core.adoc#oauth2Client-client-registration[ClientRegistration] properties.
|
||||
|
||||
|===
|
||||
|Spring Boot |ClientRegistration
|
||||
|Spring Boot 2.x |ClientRegistration
|
||||
|
||||
|`spring.security.oauth2.client.registration._[registrationId]_`
|
||||
|`registrationId`
|
||||
@@ -204,7 +204,7 @@ There are some OAuth 2.0 Providers that support multi-tenancy, which results in
|
||||
|
||||
For example, an OAuth Client registered with Okta is assigned to a specific sub-domain and have their own protocol endpoints.
|
||||
|
||||
For these cases, Spring Boot provides the following base property for configuring custom provider properties: `spring.security.oauth2.client.provider._[providerId]_`.
|
||||
For these cases, Spring Boot 2.x provides the following base property for configuring custom provider properties: `spring.security.oauth2.client.provider._[providerId]_`.
|
||||
|
||||
The following listing shows an example:
|
||||
|
||||
@@ -231,9 +231,9 @@ spring:
|
||||
|
||||
|
||||
[[webflux-oauth2-login-override-boot-autoconfig]]
|
||||
== Overriding Spring Boot Auto-configuration
|
||||
== Overriding Spring Boot 2.x Auto-configuration
|
||||
|
||||
The Spring Boot auto-configuration class for OAuth Client support is `ReactiveOAuth2ClientAutoConfiguration`.
|
||||
The Spring Boot 2.x auto-configuration class for OAuth Client support is `ReactiveOAuth2ClientAutoConfiguration`.
|
||||
|
||||
It performs the following tasks:
|
||||
|
||||
@@ -469,9 +469,9 @@ class OAuth2LoginConfig {
|
||||
|
||||
|
||||
[[webflux-oauth2-login-javaconfig-wo-boot]]
|
||||
== Java Configuration without Spring Boot
|
||||
== Java Configuration without Spring Boot 2.x
|
||||
|
||||
If you are not able to use Spring Boot and would like to configure one of the pre-defined providers in `CommonOAuth2Provider` (for example, Google), apply the following configuration:
|
||||
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
|
||||
[tabs]
|
||||
|
||||
@@ -1331,12 +1331,6 @@ Reference to an `OpaqueTokenAuthenticationConverter`. Responsible for converting
|
||||
== <relying-party-registrations>
|
||||
The container element for relying party(ies) registered (xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[ClientRegistration]) with a SAML 2.0 Identity Provider.
|
||||
|
||||
[[nsa-relying-party-registrations-attributes]]
|
||||
=== <relying-party-registrations> Attributes
|
||||
|
||||
[[nsa-relying-party-registrations-id]]
|
||||
* **id**
|
||||
The ID that uniquely identifies the `RelyingPartyRegistrationRepository`.
|
||||
|
||||
[[nsa-relying-party-registrations-children]]
|
||||
=== Child Elements of <relying-party-registrations>
|
||||
|
||||
@@ -6,4 +6,4 @@ This appendix provides a reference to the elements available in the security nam
|
||||
If you haven't used the namespace before, please read the xref:servlet/configuration/xml-namespace.adoc#ns-config[introductory chapter] on namespace configuration, as this is intended as a supplement to the information there.
|
||||
Using a good quality XML editor while editing a configuration based on the schema is recommended as this will provide contextual information on which elements and attributes are available as well as comments explaining their purpose.
|
||||
The namespace is written in https://relaxng.org/[RELAX NG] Compact format and later converted into an XSD schema.
|
||||
If you are familiar with this format, you may wish to examine the https://raw.githubusercontent.com/spring-projects/spring-security/main/config/src/main/resources/org/springframework/security/config/spring-security-6.3.rnc[schema file] directly.
|
||||
If you are familiar with this format, you may wish to examine the https://raw.githubusercontent.com/spring-projects/spring-security/main/config/src/main/resources/org/springframework/security/config/spring-security-6.2.rnc[schema file] directly.
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
[[servlet-authentication-caching-user-details]]
|
||||
= Caching `UserDetails`
|
||||
|
||||
Spring Security provides support for caching `UserDetails` with <<servlet-authentication-caching-user-details-service,`CachingUserDetailsService`>>.
|
||||
Alternatively, you can use Spring Framework's <<servlet-authentication-caching-user-details-cacheable,`@Cacheable`>> annotation.
|
||||
In either case, you will need to <<servlet-authentication-caching-user-details-credential-erasure,disable credential erasure>> in order to validate passwords retrieved from the cache.
|
||||
|
||||
[[servlet-authentication-caching-user-details-service]]
|
||||
== `CachingUserDetailsService`
|
||||
|
||||
Spring Security's `CachingUserDetailsService` implements xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[UserDetailsService] to provide support for caching `UserDetails`.
|
||||
`CachingUserDetailsService` provides caching support for `UserDetails` by delegating to the provided `UserDetailsService`.
|
||||
The result is then stored in a `UserCache` to reduce computation in subsequent calls.
|
||||
|
||||
The following example simply defines a `@Bean` that encapsulates a concrete implementation of `UserDetailsService` and a `UserCache` for caching the `UserDetails`:
|
||||
|
||||
.Provide a `CachingUserDetailsService` `@Bean`
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public CachingUserDetailsService cachingUserDetailsService(UserCache userCache) {
|
||||
UserDetailsService delegate = ...;
|
||||
CachingUserDetailsService service = new CachingUserDetailsService(delegate);
|
||||
service.setUserCache(userCache);
|
||||
return service;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun cachingUserDetailsService(userCache: UserCache): CachingUserDetailsService {
|
||||
val delegate: UserDetailsService = ...
|
||||
val service = CachingUserDetailsService(delegate)
|
||||
service.userCache = userCache
|
||||
return service
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[servlet-authentication-caching-user-details-cacheable]]
|
||||
== `@Cacheable`
|
||||
|
||||
An alternative approach would be to use Spring Framework's {spring-framework-reference-url}integration.html#cache-annotations-cacheable[`@Cacheable`] in your `UserDetailsService` implementation to cache `UserDetails` by `username`.
|
||||
The benefit to this approach is simpler configuration, especially if you are already using caching elsewhere in your application.
|
||||
|
||||
The following example assumes caching is already configured, and annotates the `loadUserByUsername` with `@Cacheable`:
|
||||
|
||||
.`UserDetailsService` annotated with `@Cacheable`
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Service
|
||||
public class MyCustomUserDetailsImplementation implements UserDetailsService {
|
||||
|
||||
@Override
|
||||
@Cacheable
|
||||
public UserDetails loadUserByUsername(String username) {
|
||||
// some logic here to get the actual user details
|
||||
return userDetails;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Service
|
||||
class MyCustomUserDetailsImplementation : UserDetailsService {
|
||||
|
||||
@Cacheable
|
||||
override fun loadUserByUsername(username: String): UserDetails {
|
||||
// some logic here to get the actual user details
|
||||
return userDetails
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[servlet-authentication-caching-user-details-credential-erasure]]
|
||||
== Disable Credential Erasure
|
||||
|
||||
Whether you use <<servlet-authentication-caching-user-details-service,`CachingUserDetailsService`>> or <<servlet-authentication-caching-user-details-cacheable,`@Cacheable`>>, you will need to disable xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager-erasing-credentials[credential erasure] so that the `UserDetails` will contain a `password` to be validated when retrieved from the cache.
|
||||
The following example disables credential erasure for the global `AuthenticationManager` by configuring the `AuthenticationManagerBuilder` provided by Spring Security:
|
||||
|
||||
.Disable credential erasure for the global `AuthenticationManager`
|
||||
[tabs]
|
||||
=====
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// ...
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
// Return a UserDetailsService that caches users
|
||||
// ...
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void configure(AuthenticationManagerBuilder builder) {
|
||||
builder.eraseCredentials(false);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
// ...
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun userDetailsService(): UserDetailsService {
|
||||
// Return a UserDetailsService that caches users
|
||||
// ...
|
||||
}
|
||||
|
||||
@Autowired
|
||||
fun configure(builder: AuthenticationManagerBuilder) {
|
||||
builder.eraseCredentials(false)
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
=====
|
||||
@@ -2,7 +2,7 @@
|
||||
= UserDetailsService
|
||||
|
||||
{security-api-url}org/springframework/security/core/userdetails/UserDetailsService.html[`UserDetailsService`] is used by xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] for retrieving a username, a password, and other attributes for authenticating with a username and password.
|
||||
Spring Security provides xref:servlet/authentication/passwords/in-memory.adoc#servlet-authentication-inmemory[in-memory], xref:servlet/authentication/passwords/jdbc.adoc#servlet-authentication-jdbc[JDBC], and xref:servlet/authentication/passwords/caching.adoc#servlet-authentication-caching-user-details[caching] implementations of `UserDetailsService`.
|
||||
Spring Security provides xref:servlet/authentication/passwords/in-memory.adoc#servlet-authentication-inmemory[in-memory] and xref:servlet/authentication/passwords/jdbc.adoc#servlet-authentication-jdbc[JDBC] implementations of `UserDetailsService`.
|
||||
|
||||
You can define custom authentication by exposing a custom `UserDetailsService` as a bean.
|
||||
For example, the following listing customizes authentication, assuming that `CustomUserDetailsService` implements `UserDetailsService`:
|
||||
|
||||
@@ -50,7 +50,7 @@ If you have more than one in your application context, you need to specify which
|
||||
|
||||
[[remember-me-persistent-token]]
|
||||
== Persistent Token Approach
|
||||
This approach is based on the article titled http://jaspan.com/improved_persistent_login_cookie_best_practice[http://jaspan.com/improved_persistent_login_cookie_best_practice], with some minor modifications. (Essentially, the username is not included in the cookie, to prevent exposing a valid login name unnecessarily.
|
||||
This approach is based on the article titled http://jaspan.com/improved_persistent_login_cookie_best_practice[http://jaspan.com/improved_persistent_login_cookie_best_practice], with some minor modifications. (Essentially, the username is not included in the cookie, to prevent exposing a valid login name unecessarily.
|
||||
There is a discussion on this in the comments section of this article.)
|
||||
To use the this approach with namespace configuration, supply a datasource reference:
|
||||
|
||||
|
||||
@@ -253,11 +253,11 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
static RoleHierarchy roleHierarchy() {
|
||||
return RoleHierarchyImpl.withDefaultRolePrefix()
|
||||
.role("ADMIN").implies("STAFF")
|
||||
.role("STAFF").implies("USER")
|
||||
.role("USER").implies("GUEST")
|
||||
.build();
|
||||
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
|
||||
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
|
||||
"ROLE_STAFF > ROLE_USER\n" +
|
||||
"ROLE_USER > ROLE_GUEST");
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
// and, if using method security also add
|
||||
@@ -274,14 +274,14 @@ Xml::
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
<bean id="roleHierarchy"
|
||||
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl" factory-method="fromHierarchy">
|
||||
<constructor-arg>
|
||||
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
|
||||
<property name="hierarchy">
|
||||
<value>
|
||||
ROLE_ADMIN > ROLE_STAFF
|
||||
ROLE_STAFF > ROLE_USER
|
||||
ROLE_USER > ROLE_GUEST
|
||||
</value>
|
||||
</constructor-arg>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- and, if using method security also add -->
|
||||
|
||||
@@ -8,7 +8,6 @@ The advanced authorization capabilities within Spring Security represent one of
|
||||
Irrespective of how you choose to authenticate (whether using a Spring Security-provided mechanism and provider or integrating with a container or other non-Spring Security authentication authority), the authorization services can be used within your application in a consistent and simple way.
|
||||
|
||||
You should consider attaching authorization rules to xref:servlet/authorization/authorize-http-requests.adoc[request URIs] and xref:servlet/authorization/method-security.adoc[methods] to begin.
|
||||
In either case, you can listen and react to xref:servlet/authorization/events.adoc[authorization events] that each authorization check publishes.
|
||||
Below there is also wealth of detail about xref:servlet/authorization/architecture.adoc[how Spring Security authorization works] and how, having established a basic model, it can be fine-tuned.
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ The `OAuth2AuthorizationRequestRedirectFilter` uses an `OAuth2AuthorizationReque
|
||||
The primary role of the `OAuth2AuthorizationRequestResolver` is to resolve an `OAuth2AuthorizationRequest` from the provided web request.
|
||||
The default implementation `DefaultOAuth2AuthorizationRequestResolver` matches on the (default) path `+/oauth2/authorization/{registrationId}+`, extracting the `registrationId`, and using it to build the `OAuth2AuthorizationRequest` for the associated `ClientRegistration`.
|
||||
|
||||
Consider the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Consider the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml,attrs="-attributes"]
|
||||
----
|
||||
@@ -740,7 +740,7 @@ which is an implementation of an `OAuth2AuthorizedClientProvider` for the Client
|
||||
|
||||
=== Using the Access Token
|
||||
|
||||
Consider the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Consider the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -1005,7 +1005,7 @@ which is an implementation of an `OAuth2AuthorizedClientProvider` for the Resour
|
||||
|
||||
=== Using the Access Token
|
||||
|
||||
Consider the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Consider the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -1309,7 +1309,7 @@ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
|
||||
=== Using the Access Token
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -1435,255 +1435,3 @@ class OAuth2ResourceServerController {
|
||||
|
||||
[TIP]
|
||||
If you need to resolve the `Jwt` assertion from a different source, you can provide `JwtBearerOAuth2AuthorizedClientProvider.setJwtAssertionResolver()` with a custom `Function<OAuth2AuthorizationContext, Jwt>`.
|
||||
|
||||
[[oauth2Client-token-exchange-grant]]
|
||||
== Token Exchange
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Please refer to OAuth 2.0 Token Exchange for further details on the https://datatracker.ietf.org/doc/html/rfc8693[Token Exchange] grant.
|
||||
====
|
||||
|
||||
|
||||
=== Requesting an Access Token
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Please refer to the https://datatracker.ietf.org/doc/html/rfc8693#section-2[Token Exchange Request and Response] protocol flow for the Token Exchange grant.
|
||||
====
|
||||
|
||||
The default implementation of `OAuth2AccessTokenResponseClient` for the Token Exchange grant is `DefaultTokenExchangeTokenResponseClient`, which uses a `RestOperations` when requesting an access token at the Authorization Server’s Token Endpoint.
|
||||
|
||||
The `DefaultTokenExchangeTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response.
|
||||
|
||||
|
||||
=== Customizing the Access Token Request
|
||||
|
||||
If you need to customize the pre-processing of the Token Request, you can provide `DefaultTokenExchangeTokenResponseClient.setRequestEntityConverter()` with a custom `Converter<TokenExchangeGrantRequest, RequestEntity<?>>`.
|
||||
The default implementation `TokenExchangeGrantRequestEntityConverter` builds a `RequestEntity` representation of a https://datatracker.ietf.org/doc/html/rfc8693#section-2.1[OAuth 2.0 Access Token Request].
|
||||
However, providing a custom `Converter`, would allow you to extend the Token Request and add custom parameter(s).
|
||||
|
||||
To customize only the parameters of the request, you can provide `TokenExchangeGrantRequestEntityConverter.setParametersConverter()` with a custom `Converter<TokenExchangeGrantRequest, MultiValueMap<String, String>>` to completely override the parameters sent with the request. This is often simpler than constructing a `RequestEntity` directly.
|
||||
|
||||
[TIP]
|
||||
If you prefer to only add additional parameters, you can provide `TokenExchangeGrantRequestEntityConverter.addParametersConverter()` with a custom `Converter<TokenExchangeGrantRequest, MultiValueMap<String, String>>` which constructs an aggregate `Converter`.
|
||||
|
||||
|
||||
=== Customizing the Access Token Response
|
||||
|
||||
On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultTokenExchangeTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
|
||||
The default `RestOperations` is configured as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
new FormHttpMessageConverter(),
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val restTemplate = RestTemplate(listOf(
|
||||
FormHttpMessageConverter(),
|
||||
OAuth2AccessTokenResponseHttpMessageConverter()))
|
||||
|
||||
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
|
||||
----
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request.
|
||||
====
|
||||
|
||||
`OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response.
|
||||
You can provide `OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter()` with a custom `Converter<Map<String, Object>, OAuth2AccessTokenResponse>` that is used for converting the OAuth 2.0 Access Token Response parameters to an `OAuth2AccessTokenResponse`.
|
||||
|
||||
`OAuth2ErrorResponseErrorHandler` is a `ResponseErrorHandler` that can handle an OAuth 2.0 Error, eg. 400 Bad Request.
|
||||
It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error parameters to an `OAuth2Error`.
|
||||
|
||||
Whether you customize `DefaultTokenExchangeTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeTokenResponseClient = ...
|
||||
|
||||
TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider();
|
||||
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient);
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build();
|
||||
|
||||
...
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
val tokenExchangeTokenResponseClient: OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> = ...
|
||||
|
||||
val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
|
||||
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient)
|
||||
|
||||
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build()
|
||||
|
||||
...
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
======
|
||||
|
||||
[[token-exchange-grant-access-token]]
|
||||
=== Using the Access Token
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
okta:
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
|
||||
scope: read
|
||||
provider:
|
||||
okta:
|
||||
token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
|
||||
----
|
||||
|
||||
...and the `OAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
ClientRegistrationRepository clientRegistrationRepository,
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
|
||||
TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
|
||||
new TokenExchangeOAuth2AuthorizedClientProvider();
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build();
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
new DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
return authorizedClientManager;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun authorizedClientManager(
|
||||
clientRegistrationRepository: ClientRegistrationRepository,
|
||||
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
|
||||
val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
|
||||
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build()
|
||||
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RestController
|
||||
public class OAuth2ResourceServerController {
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizedClientManager authorizedClientManager;
|
||||
|
||||
@GetMapping("/resource")
|
||||
public String resource(JwtAuthenticationToken jwtAuthentication) {
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(jwtAuthentication)
|
||||
.build();
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
|
||||
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class OAuth2ResourceServerController {
|
||||
|
||||
@Autowired
|
||||
private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager
|
||||
|
||||
@GetMapping("/resource")
|
||||
fun resource(jwtAuthentication: JwtAuthenticationToken?): String {
|
||||
val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(jwtAuthentication)
|
||||
.build()
|
||||
val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
|
||||
val accessToken: OAuth2AccessToken = authorizedClient.accessToken
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`TokenExchangeOAuth2AuthorizedClientProvider` resolves the subject token (as an `OAuth2Token`) via `OAuth2AuthorizationContext.getPrincipal().getPrincipal()` by default, hence the use of `JwtAuthenticationToken` in the preceding example.
|
||||
An actor token is not resolved by default.
|
||||
|
||||
[TIP]
|
||||
If you need to resolve the subject token from a different source, you can provide `TokenExchangeOAuth2AuthorizedClientProvider.setSubjectTokenResolver()` with a custom `Function<OAuth2AuthorizationContext, OAuth2Token>`.
|
||||
|
||||
[TIP]
|
||||
If you need to resolve an actor token, you can provide `TokenExchangeOAuth2AuthorizedClientProvider.setActorTokenResolver()` with a custom `Function<OAuth2AuthorizationContext, OAuth2Token>`.
|
||||
|
||||
@@ -18,7 +18,7 @@ is supplied by the `com.nimbusds.jose.jwk.JWK` resolver associated with `NimbusJ
|
||||
|
||||
=== Authenticate using `private_key_jwt`
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
@@ -96,7 +96,7 @@ tokenResponseClient.setRequestEntityConverter(requestEntityConverter)
|
||||
|
||||
=== Authenticate using `client_secret_jwt`
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
|
||||
@@ -60,7 +60,7 @@ The name may be used in certain scenarios, such as when displaying the name of t
|
||||
which contains the cryptographic key(s) used to verify the https://tools.ietf.org/html/rfc7515[JSON Web Signature (JWS)] of the ID Token and (optionally) the UserInfo Response.
|
||||
<12> `issuerUri`: Returns the issuer identifier URI for the OpenID Connect 1.0 provider or the OAuth 2.0 Authorization Server.
|
||||
<13> `configurationMetadata`: The https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Provider Configuration Information].
|
||||
This information is available only if the Spring Boot property `spring.security.oauth2.client.provider.[providerId].issuerUri` is configured.
|
||||
This information is available only if the Spring Boot 2.x property `spring.security.oauth2.client.provider.[providerId].issuerUri` is configured.
|
||||
<14> `(userInfoEndpoint)uri`: The UserInfo Endpoint URI used to access the claims and attributes of the authenticated end-user.
|
||||
<15> `(userInfoEndpoint)authenticationMethod`: The authentication method used when sending the access token to the UserInfo Endpoint.
|
||||
The supported values are *header*, *form*, and *query*.
|
||||
@@ -103,7 +103,7 @@ Client registration information is ultimately stored and owned by the associated
|
||||
This repository provides the ability to retrieve a subset of the primary client registration information, which is stored with the Authorization Server.
|
||||
====
|
||||
|
||||
Spring Boot auto-configuration binds each of the properties under `spring.security.oauth2.client.registration._[registrationId]_` to an instance of `ClientRegistration` and then composes each of the `ClientRegistration` instance(s) within a `ClientRegistrationRepository`.
|
||||
Spring Boot 2.x auto-configuration binds each of the properties under `spring.security.oauth2.client.registration._[registrationId]_` to an instance of `ClientRegistration` and then composes each of the `ClientRegistration` instance(s) within a `ClientRegistrationRepository`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -231,7 +231,7 @@ class OAuth2ClientController {
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Spring Boot auto-configuration registers an `OAuth2AuthorizedClientRepository` or an `OAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`.
|
||||
Spring Boot 2.x auto-configuration registers an `OAuth2AuthorizedClientRepository` or an `OAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`.
|
||||
However, the application can override and register a custom `OAuth2AuthorizedClientRepository` or `OAuth2AuthorizedClientService` `@Bean`.
|
||||
====
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ At a high-level, the core features available are:
|
||||
* https://tools.ietf.org/html/rfc6749#section-1.3.4[Client Credentials]
|
||||
* https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials]
|
||||
* https://datatracker.ietf.org/doc/html/rfc7523#section-2.1[JWT Bearer]
|
||||
* https://datatracker.ietf.org/doc/html/rfc8693#section-2.1[Token Exchange]
|
||||
|
||||
.Client Authentication support
|
||||
* https://datatracker.ietf.org/doc/html/rfc7523#section-2.2[JWT Bearer]
|
||||
|
||||
@@ -491,7 +491,7 @@ With the above configuration, the application now supports two additional endpoi
|
||||
====
|
||||
The presence of the `openid` scope in the above configuration indicates that OpenID Connect 1.0 should be used.
|
||||
This instructs Spring Security to use OIDC-specific components (such as `OidcUserService`) during request processing.
|
||||
Without this scope, Spring Security will use OAuth2-specific components (such as `DefaultOAuth2UserService`) instead.
|
||||
Without this scope, Spring Security will use OAuth2-specific components (such as `OAuth2UserService`) instead.
|
||||
====
|
||||
|
||||
[[oauth2-client-access-protected-resources]]
|
||||
@@ -708,7 +708,7 @@ class MessagesController(private val webClient: WebClient) {
|
||||
.uri("http://localhost:8090/messages")
|
||||
.attributes(clientRegistrationId("my-oauth2-client"))
|
||||
.retrieve()
|
||||
.toEntityList<Message>()
|
||||
.toEntityList(Message::class.java)
|
||||
.block()!!
|
||||
}
|
||||
|
||||
@@ -933,7 +933,7 @@ class MessagesController(private val webClient: WebClient) {
|
||||
return webClient.get()
|
||||
.uri("http://localhost:8090/messages")
|
||||
.retrieve()
|
||||
.toEntityList<Message>()
|
||||
.toEntityList(Message::class.java)
|
||||
.block()!!
|
||||
}
|
||||
|
||||
@@ -953,7 +953,7 @@ This is because it can be derived from the currently logged in user.
|
||||
=== Enable an Extension Grant Type
|
||||
|
||||
A common use case involves enabling and/or configuring an extension grant type.
|
||||
For example, Spring Security provides support for the `jwt-bearer` and `token-exchange` grant types, but does not enable them by default because they are not part of the core OAuth 2.0 specification.
|
||||
For example, Spring Security provides support for the `jwt-bearer` grant type, but does not enable it by default because it is not part of the core OAuth 2.0 specification.
|
||||
|
||||
With Spring Security 6.2 and later, we can simply publish a bean for one or more `OAuth2AuthorizedClientProvider` and they will be picked up automatically.
|
||||
The following example simply enables the `jwt-bearer` grant type:
|
||||
@@ -1356,18 +1356,12 @@ Spring Security automatically resolves the following generic types of `OAuth2Acc
|
||||
* `OAuth2ClientCredentialsGrantRequest` (see `DefaultClientCredentialsTokenResponseClient`)
|
||||
* `OAuth2PasswordGrantRequest` (see `DefaultPasswordTokenResponseClient`)
|
||||
* `JwtBearerGrantRequest` (see `DefaultJwtBearerTokenResponseClient`)
|
||||
* `TokenExchangeGrantRequest` (see `DefaultTokenExchangeTokenResponseClient`)
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Publishing a bean of type `OAuth2AccessTokenResponseClient<JwtBearerGrantRequest>` will automatically enable the `jwt-bearer` grant type without the need to <<oauth2-client-enable-extension-grant-type,configure it separately>>.
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Publishing a bean of type `OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest>` will automatically enable the `token-exchange` grant type without the need to <<oauth2-client-enable-extension-grant-type,configure it separately>>.
|
||||
====
|
||||
|
||||
[[oauth2-client-customize-rest-operations]]
|
||||
=== Customize the `RestOperations` used by OAuth2 Client Components
|
||||
|
||||
@@ -1433,15 +1427,6 @@ public class SecurityConfig {
|
||||
return accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
|
||||
DefaultTokenExchangeTokenResponseClient accessTokenResponseClient =
|
||||
new DefaultTokenExchangeTokenResponseClient();
|
||||
accessTokenResponseClient.setRestOperations(restTemplate());
|
||||
|
||||
return accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
// ...
|
||||
@@ -1497,14 +1482,6 @@ class SecurityConfig {
|
||||
return accessTokenResponseClient
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
|
||||
val accessTokenResponseClient = DefaultTokenExchangeTokenResponseClient()
|
||||
accessTokenResponseClient.setRestOperations(restTemplate())
|
||||
|
||||
return accessTokenResponseClient
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun restTemplate(): RestTemplate {
|
||||
// ...
|
||||
@@ -1584,14 +1561,6 @@ public class SecurityConfig {
|
||||
new JwtBearerOAuth2AuthorizedClientProvider();
|
||||
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient);
|
||||
|
||||
DefaultTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient =
|
||||
new DefaultTokenExchangeTokenResponseClient();
|
||||
tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate());
|
||||
|
||||
TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
|
||||
new TokenExchangeOAuth2AuthorizedClientProvider();
|
||||
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient);
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.authorizationCode()
|
||||
@@ -1605,7 +1574,6 @@ public class SecurityConfig {
|
||||
.accessTokenResponseClient(passwordAccessTokenResponseClient)
|
||||
)
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build();
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
@@ -1676,12 +1644,6 @@ class SecurityConfig {
|
||||
val jwtBearerAuthorizedClientProvider = JwtBearerOAuth2AuthorizedClientProvider()
|
||||
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient)
|
||||
|
||||
val tokenExchangeAccessTokenResponseClient = DefaultTokenExchangeTokenResponseClient()
|
||||
tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate())
|
||||
|
||||
val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
|
||||
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient)
|
||||
|
||||
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.authorizationCode()
|
||||
.refreshToken { refreshToken ->
|
||||
@@ -1694,7 +1656,6 @@ class SecurityConfig {
|
||||
password.accessTokenResponseClient(passwordAccessTokenResponseClient)
|
||||
}
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build()
|
||||
|
||||
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
|
||||
|
||||
@@ -660,13 +660,7 @@ public class OAuth2LoginSecurityConfig {
|
||||
// 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
|
||||
|
||||
// 3) Create a copy of oidcUser but use the mappedAuthorities instead
|
||||
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
|
||||
String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName();
|
||||
if (StringUtils.hasText(userNameAttributeName)) {
|
||||
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo(), userNameAttributeName);
|
||||
} else {
|
||||
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
|
||||
}
|
||||
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
|
||||
|
||||
return oidcUser;
|
||||
};
|
||||
@@ -700,7 +694,7 @@ class OAuth2LoginSecurityConfig {
|
||||
|
||||
return OAuth2UserService { userRequest ->
|
||||
// Delegate to the default implementation for loading a user
|
||||
val oidcUser = delegate.loadUser(userRequest)
|
||||
var oidcUser = delegate.loadUser(userRequest)
|
||||
|
||||
val accessToken = userRequest.accessToken
|
||||
val mappedAuthorities = HashSet<GrantedAuthority>()
|
||||
@@ -709,13 +703,9 @@ class OAuth2LoginSecurityConfig {
|
||||
// 1) Fetch the authority information from the protected resource using accessToken
|
||||
// 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
|
||||
// 3) Create a copy of oidcUser but use the mappedAuthorities instead
|
||||
val providerDetails = userRequest.getClientRegistration().getProviderDetails()
|
||||
val userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName()
|
||||
if (StringUtils.hasText(userNameAttributeName)) {
|
||||
DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo, userNameAttributeName)
|
||||
} else {
|
||||
DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
|
||||
}
|
||||
oidcUser = DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
|
||||
|
||||
oidcUser
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -940,4 +930,4 @@ If more than one `ClientRegistration` is configured for OpenID Connect 1.0 Authe
|
||||
====
|
||||
|
||||
[[oauth2login-advanced-oidc-logout]]
|
||||
Then, you can proceed to configure xref:servlet/oauth2/login/logout.adoc[logout]
|
||||
Then, you can proceed to configure xref:reactive/oauth2/login/logout.adoc[logout]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
= Core Configuration
|
||||
|
||||
[[oauth2login-sample-boot]]
|
||||
== Spring Boot Sample
|
||||
== Spring Boot 2.x Sample
|
||||
|
||||
Spring Boot brings full auto-configuration capabilities for OAuth 2.0 Login.
|
||||
Spring Boot 2.x brings full auto-configuration capabilities for OAuth 2.0 Login.
|
||||
|
||||
This section shows how to configure the {gh-samples-url}/servlet/spring-boot/java/oauth2/login[*OAuth 2.0 Login sample*] by using _Google_ as the _Authentication Provider_ and covers the following topics:
|
||||
|
||||
@@ -78,7 +78,7 @@ spring:
|
||||
[[oauth2login-sample-boot-application]]
|
||||
=== Boot up the Application
|
||||
|
||||
Launch the Spring Boot sample and go to `http://localhost:8080`.
|
||||
Launch the Spring Boot 2.x sample and go to `http://localhost:8080`.
|
||||
You are then redirected to the default _auto-generated_ login page, which displays a link for Google.
|
||||
|
||||
Click on the Google link, and you are then redirected to Google for authentication.
|
||||
@@ -91,12 +91,12 @@ At this point, the OAuth Client retrieves your email address and basic profile i
|
||||
|
||||
|
||||
[[oauth2login-boot-property-mappings]]
|
||||
== Spring Boot Property Mappings
|
||||
== Spring Boot 2.x Property Mappings
|
||||
|
||||
The following table outlines the mapping of the Spring Boot OAuth Client properties to the xref:servlet/oauth2/client/index.adoc#oauth2Client-client-registration[ClientRegistration] properties.
|
||||
The following table outlines the mapping of the Spring Boot 2.x OAuth Client properties to the xref:servlet/oauth2/client/index.adoc#oauth2Client-client-registration[ClientRegistration] properties.
|
||||
|
||||
|===
|
||||
|Spring Boot |ClientRegistration
|
||||
|Spring Boot 2.x |ClientRegistration
|
||||
|
||||
|`spring.security.oauth2.client.registration._[registrationId]_`
|
||||
|`registrationId`
|
||||
@@ -203,7 +203,7 @@ There are some OAuth 2.0 Providers that support multi-tenancy, which results in
|
||||
|
||||
For example, an OAuth Client registered with Okta is assigned to a specific sub-domain and have their own protocol endpoints.
|
||||
|
||||
For these cases, Spring Boot provides the following base property for configuring custom provider properties: `spring.security.oauth2.client.provider._[providerId]_`.
|
||||
For these cases, Spring Boot 2.x provides the following base property for configuring custom provider properties: `spring.security.oauth2.client.provider._[providerId]_`.
|
||||
|
||||
The following listing shows an example:
|
||||
|
||||
@@ -228,9 +228,9 @@ spring:
|
||||
<1> The base property (`spring.security.oauth2.client.provider.okta`) allows for custom configuration of protocol endpoint locations.
|
||||
|
||||
[[oauth2login-override-boot-autoconfig]]
|
||||
== Overriding Spring Boot Auto-configuration
|
||||
== Overriding Spring Boot 2.x Auto-configuration
|
||||
|
||||
The Spring Boot auto-configuration class for OAuth Client support is `OAuth2ClientAutoConfiguration`.
|
||||
The Spring Boot 2.x auto-configuration class for OAuth Client support is `OAuth2ClientAutoConfiguration`.
|
||||
|
||||
It performs the following tasks:
|
||||
|
||||
@@ -457,9 +457,9 @@ class OAuth2LoginConfig {
|
||||
|
||||
|
||||
[[oauth2login-javaconfig-wo-boot]]
|
||||
== Java Configuration without Spring Boot
|
||||
== Java Configuration without Spring Boot 2.x
|
||||
|
||||
If you are not able to use Spring Boot and would like to configure one of the pre-defined providers in `CommonOAuth2Provider` (for example, Google), apply the following configuration:
|
||||
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
|
||||
[tabs]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[[servlet-saml2login-logout]]
|
||||
= Performing Single Logout
|
||||
|
||||
Among its xref:servlet/authentication/logout.adoc[other logout mechanisms], Spring Security ships with support for RP- and AP-initiated SAML 2.0 Single Logout.
|
||||
Spring Security ships with support for RP- and AP-initiated SAML 2.0 Single Logout.
|
||||
|
||||
Briefly, there are two use cases Spring Security supports:
|
||||
|
||||
@@ -22,201 +22,61 @@ To use Spring Security's SAML 2.0 Single Logout feature, you will need the follo
|
||||
* Second, the asserting party should be configured to sign and POST `saml2:LogoutRequest` s and `saml2:LogoutResponse` s your application's `/logout/saml2/slo` endpoint
|
||||
* Third, your application must have a PKCS#8 private key and X.509 certificate for signing `saml2:LogoutRequest` s and `saml2:LogoutResponse` s
|
||||
|
||||
You can achieve this in Spring Boot in the following way:
|
||||
You can begin from the initial minimal example and add the following configuration:
|
||||
|
||||
[source,yaml]
|
||||
[source,java]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
saml2:
|
||||
relyingparty:
|
||||
registration:
|
||||
metadata:
|
||||
signing.credentials: <3>
|
||||
- private-key-location: classpath:credentials/rp-private.key
|
||||
certificate-location: classpath:credentials/rp-certificate.crt
|
||||
singlelogout.url: "{baseUrl}/logout/saml2/slo" <2>
|
||||
assertingparty:
|
||||
metadata-uri: https://ap.example.com/metadata <1>
|
||||
@Value("${private.key}") RSAPrivateKey key;
|
||||
@Value("${public.certificate}") X509Certificate certificate;
|
||||
|
||||
@Bean
|
||||
RelyingPartyRegistrationRepository registrations() {
|
||||
Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate);
|
||||
RelyingPartyRegistration registration = RelyingPartyRegistrations
|
||||
.fromMetadataLocation("https://ap.example.org/metadata")
|
||||
.registrationId("id")
|
||||
.singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo")
|
||||
.signingX509Credentials((signing) -> signing.add(credential)) <1>
|
||||
.build();
|
||||
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.saml2Login(withDefaults())
|
||||
.saml2Logout(withDefaults()); <2>
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> - The metadata URI of the IDP, which will indicate to your application its support of SLO
|
||||
<2> - The SLO endpoint in your application
|
||||
<3> - The signing credentials to sign ``<saml2:LogoutRequest>``s and ``<saml2:LogoutResponse>``s
|
||||
|
||||
[NOTE]
|
||||
----
|
||||
An asserting party supports Single Logout if their metadata includes the `<SingleLogoutService>` element in their metadata.
|
||||
----
|
||||
|
||||
And that's it!
|
||||
|
||||
Spring Security's logout support offers a number of configuration points.
|
||||
Consider the following use cases:
|
||||
|
||||
* Understand how the above <<_startup_expectations, minimal configuration works>>
|
||||
* Get a picture of <<architecture, the overall architecture>>
|
||||
* Allow users to <<separating-local-saml2-logout, logout out of the app only>>
|
||||
* Customize <<_configuring_logout_endpoints, logout endpoints>>
|
||||
* Storing `<saml2:LogoutRequests>` somewhere <<_customizing_storage, other than the session>>
|
||||
|
||||
=== Startup Expectations
|
||||
|
||||
When these properties are used, in addition to login, SAML 2.0 Service Provider will automatically configure itself facilitate logout by way of ``<saml2:LogoutRequest>``s and ``<saml2:LogoutResponse>``s using either RP- or AP-initiated logout.
|
||||
|
||||
It achieves this through a deterministic startup process:
|
||||
|
||||
1. Query the Identity Server Metadata endpoint for the `<SingleLogoutService>` element
|
||||
2. Scan the metadata and cache any public signature verification keys
|
||||
3. Prepare the appropriate endpoints
|
||||
|
||||
A consequence of this process is that the identity server must be up and receiving requests in order for Service Provider to successfully start up.
|
||||
|
||||
[NOTE]
|
||||
If the identity server is down when Service Provider queries it (given appropriate timeouts), then startup will fail.
|
||||
<1> - First, add your signing key to the `RelyingPartyRegistration` instance or to xref:servlet/saml2/login/overview.adoc#servlet-saml2login-rpr-duplicated[multiple instances]
|
||||
<2> - Second, indicate that your application wants to use SAML SLO to logout the end user
|
||||
|
||||
=== Runtime Expectations
|
||||
|
||||
Given the above configuration any logged-in user can send a `POST /logout` to your application to perform RP-initiated SLO.
|
||||
Given the above configuration any logged in user can send a `POST /logout` to your application to perform RP-initiated SLO.
|
||||
Your application will then do the following:
|
||||
|
||||
1. Logout the user and invalidate the session
|
||||
2. Produce a `<saml2:LogoutRequest>` and POST it to the associated asserting party's SLO endpoint
|
||||
3. Then, if the asserting party responds with a `<saml2:LogoutResponse>`, the application with verify it and redirect to the configured success endpoint
|
||||
2. Use a `Saml2LogoutRequestResolver` to create, sign, and serialize a `<saml2:LogoutRequest>` based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] associated with the currently logged-in user.
|
||||
3. Send a redirect or post to the asserting party based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`]
|
||||
4. Deserialize, verify, and process the `<saml2:LogoutResponse>` sent by the asserting party
|
||||
5. Redirect to any configured successful logout endpoint
|
||||
|
||||
Also, your application can participate in an AP-initiated logout when the asserting party sends a `<saml2:LogoutRequest>` to `/logout/saml2/slo`.
|
||||
When this happens, your application will do the following:
|
||||
Also, your application can participate in an AP-initiated logout when the asserting party sends a `<saml2:LogoutRequest>` to `/logout/saml2/slo`:
|
||||
|
||||
1. Verify the `<saml2:LogoutRequest>`
|
||||
1. Use a `Saml2LogoutRequestHandler` to deserialize, verify, and process the `<saml2:LogoutRequest>` sent by the asserting party
|
||||
2. Logout the user and invalidate the session
|
||||
3. Produce a `<saml2:LogoutResponse>` and POST it back to the asserting party's SLO endpoint
|
||||
3. Create, sign, and serialize a `<saml2:LogoutResponse>` based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] associated with the just logged-out user
|
||||
4. Send a redirect or post to the asserting party based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`]
|
||||
|
||||
== Minimal Configuration Sans Boot
|
||||
|
||||
Instead of Boot properties, you can also achieve the same outcome by publishing the beans directly like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
@Value("${private.key}") RSAPrivateKey key;
|
||||
@Value("${public.certificate}") X509Certificate certificate;
|
||||
|
||||
@Bean
|
||||
RelyingPartyRegistrationRepository registrations() {
|
||||
Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate);
|
||||
RelyingPartyRegistration registration = RelyingPartyRegistrations
|
||||
.fromMetadataLocation("https://ap.example.org/metadata") <1>
|
||||
.registrationId("metadata")
|
||||
.singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") <2>
|
||||
.signingX509Credentials((signing) -> signing.add(credential)) <3>
|
||||
.build();
|
||||
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain web(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.saml2Login(withDefaults())
|
||||
.saml2Logout(withDefaults()); <4>
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class SecurityConfig(@Value("${private.key}") val key: RSAPrivateKey,
|
||||
@Value("${public.certificate}") val certificate: X509Certificate) {
|
||||
|
||||
@Bean
|
||||
fun registrations(): RelyingPartyRegistrationRepository {
|
||||
val credential = Saml2X509Credential.signing(key, certificate)
|
||||
val registration = RelyingPartyRegistrations
|
||||
.fromMetadataLocation("https://ap.example.org/metadata") <1>
|
||||
.registrationId("metadata")
|
||||
.singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") <2>
|
||||
.signingX509Credentials({ signing: List<Saml2X509Credential> -> signing.add(credential) }) <3>
|
||||
.build()
|
||||
return InMemoryRelyingPartyRegistrationRepository(registration)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun web(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
anyRequest = authenticated
|
||||
}
|
||||
saml2Login {
|
||||
|
||||
}
|
||||
saml2Logout { <4>
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> - The metadata URI of the IDP, which will indicate to your application its support of SLO
|
||||
<2> - The SLO endpoint in your application
|
||||
<3> - The signing credentials to sign ``<saml2:LogoutRequest>``s and ``<saml2:LogoutResponse>``s, which you can also add to xref:servlet/saml2/login/overview.adoc#servlet-saml2login-rpr-duplicated[multiple relying parties]
|
||||
<4> - Second, indicate that your application wants to use SAML SLO to logout the end user
|
||||
|
||||
[NOTE]
|
||||
Adding `saml2Logout` adds the capability for logout to your service provider as a whole.
|
||||
NOTE: Adding `saml2Logout` adds the capability for logout to the service provider.
|
||||
Because it is an optional capability, you need to enable it for each individual `RelyingPartyRegistration`.
|
||||
You do this by setting the `RelyingPartyRegistration.Builder#singleLogoutServiceLocation` property as seen above.
|
||||
|
||||
[[architecture]]
|
||||
== How Saml 2.0 Logout Works
|
||||
|
||||
Next, let's see the architectural components that Spring Security uses to support https://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf#page=37[SAML 2.0 Logout] in servlet-based applications, like the one we just saw.
|
||||
|
||||
For RP-initiated logout:
|
||||
|
||||
image:{icondir}/number_1.png[] Spring Security executes its xref:servlet/authentication/logout.adoc#logout-architecture[logout flow], calling its ``LogoutHandler``s to invalidate the session and perform other cleanup.
|
||||
It then invokes the {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2RelyingPartyInitiatedLogoutSuccessHandler.html[`Saml2RelyingPartyInitiatedLogoutSuccessHandler`].
|
||||
|
||||
image:{icondir}/number_2.png[] The logout success handler uses an instance of
|
||||
{security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestResolver.html[`Saml2LogoutRequestResolver`] to create, sign, and serialize a `<saml2:LogoutRequest>`.
|
||||
It uses the keys and configuration from the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] that is associated with the current `Saml2AuthenticatedPrincipal`.
|
||||
Then, it redirect-POSTs the `<saml2:LogoutRequest>` to the asserting party SLO endpoint
|
||||
|
||||
The browser hands control over to the asserting party.
|
||||
If the asserting party redirects back (which it may not), then the application proceeds to step image:{icondir}/number_3.png[].
|
||||
|
||||
image:{icondir}/number_3.png[] The {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilter.html[`Saml2LogoutResponseFilter`] deserializes, verifies, and processes the `<saml2:LogoutResponse>` with its {security-api-url}org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutResponseValidator.html[`Saml2LogoutResponseValidator`].
|
||||
|
||||
image:{icondir}/number_4.png[] If valid, then it completes the local logout flow by redirecting to `/login?logout`, or whatever has been configured.
|
||||
If invalid, then it responds with a 400.
|
||||
|
||||
For AP-initiated logout:
|
||||
|
||||
image:{icondir}/number_1.png[] The {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.html[`Saml2LogoutRequestFilter`] deserializes, verifies, and processes the `<saml2:LogoutRequest>` with its {security-api-url}org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutRequestValidator.html[`Saml2LogoutRequestValidator`].
|
||||
|
||||
image:{icondir}/number_2.png[] If valid, then the filter calls the configured ``LogoutHandler``s, invalidating the session and performing other cleanup.
|
||||
|
||||
image:{icondir}/number_3.png[] It uses a {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.html[`Saml2LogoutResponseResolver`] to create, sign and serialize a `<saml2:LogoutResponse>`.
|
||||
It uses the keys and configuration from the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] derived from the endpoint or from the contents of the `<saml2:LogoutRequest>`.
|
||||
Then, it redirect-POSTs the `<saml2:LogoutResponse>` to the asserting party SLO endpoint.
|
||||
|
||||
The browser hands control over to the asserting party.
|
||||
|
||||
image:{icondir}/number_4.png[] If invalid, then it https://github.com/spring-projects/spring-security/pull/14676[responds with a 400].
|
||||
You can do this by setting the `RelyingPartyRegistration.Builder#singleLogoutServiceLocation` property.
|
||||
|
||||
== Configuring Logout Endpoints
|
||||
|
||||
@@ -252,87 +112,10 @@ http
|
||||
.logoutResponse((response) -> response.logoutUrl("/SLOService.saml2"))
|
||||
);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
saml2Logout {
|
||||
logoutRequest {
|
||||
logoutUrl = "/SLOService.saml2"
|
||||
}
|
||||
logoutResponse {
|
||||
logoutUrl = "/SLOService.saml2"
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You should also configure these endpoints in your `RelyingPartyRegistration`.
|
||||
|
||||
Also, you can customize the endpoint for triggering logout locally like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
.saml2Logout((saml2) -> saml2.logoutUrl("/saml2/logout"));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
saml2Logout {
|
||||
logoutUrl = "/saml2/logout"
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[separating-local-saml2-logout]]
|
||||
=== Separating Local Logout from SAML 2.0 Logout
|
||||
|
||||
In some cases, you may want to expose one logout endpoint for local logout and another for RP-initiated SLO.
|
||||
Like is the case with other logout mechanisms, you can register more than one, so long as they each have a different endpoint.
|
||||
|
||||
So, for example, you can wire the DSL like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
.logout((logout) -> logout.logoutUrl("/logout"))
|
||||
.saml2Logout((saml2) -> saml2.logoutUrl("/saml2/logout"));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
logout {
|
||||
logoutUrl = "/logout"
|
||||
}
|
||||
saml2Logout {
|
||||
logoutUrl = "/saml2/logout"
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
and now if a client sends a `POST /logout`, the session will be cleared, but there won't be a `<saml2:LogoutRequest>` sent to the asserting party.
|
||||
But, if the client sends a `POST /saml2/logout`, then the application will initiate SAML 2.0 SLO as normal.
|
||||
|
||||
== Customizing `<saml2:LogoutRequest>` Resolution
|
||||
|
||||
It's common to need to set other values in the `<saml2:LogoutRequest>` than the defaults that Spring Security provides.
|
||||
@@ -346,11 +129,7 @@ By default, Spring Security will issue a `<saml2:LogoutRequest>` and supply:
|
||||
|
||||
To add other values, you can use delegation, like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) {
|
||||
@@ -368,33 +147,9 @@ Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationReposit
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun logoutRequestResolver(registrations:RelyingPartyRegistrationRepository?): Saml2LogoutRequestResolver {
|
||||
val logoutRequestResolver = OpenSaml4LogoutRequestResolver(registrations)
|
||||
logoutRequestResolver.setParametersConsumer { parameters: LogoutRequestParameters ->
|
||||
val name: String = (parameters.getAuthentication().getPrincipal() as Saml2AuthenticatedPrincipal).getFirstAttribute("CustomAttribute")
|
||||
val format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
||||
val logoutRequest: LogoutRequest = parameters.getLogoutRequest()
|
||||
val nameId: NameID = logoutRequest.getNameID()
|
||||
nameId.setValue(name)
|
||||
nameId.setFormat(format)
|
||||
}
|
||||
return logoutRequestResolver
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Then, you can supply your custom `Saml2LogoutRequestResolver` in the DSL as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
http
|
||||
.saml2Logout((saml2) -> saml2
|
||||
@@ -404,20 +159,6 @@ http
|
||||
);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
saml2Logout {
|
||||
logoutRequest {
|
||||
logoutRequestResolver = this.logoutRequestResolver
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
== Customizing `<saml2:LogoutResponse>` Resolution
|
||||
|
||||
It's common to need to set other values in the `<saml2:LogoutResponse>` than the defaults that Spring Security provides.
|
||||
@@ -431,11 +172,7 @@ By default, Spring Security will issue a `<saml2:LogoutResponse>` and supply:
|
||||
|
||||
To add other values, you can use delegation, like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationRepository registrations) {
|
||||
@@ -450,30 +187,9 @@ public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrati
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun logoutResponseResolver(registrations: RelyingPartyRegistrationRepository?): Saml2LogoutResponseResolver {
|
||||
val logoutRequestResolver = OpenSaml4LogoutResponseResolver(registrations)
|
||||
logoutRequestResolver.setParametersConsumer { LogoutResponseParameters parameters ->
|
||||
if (checkOtherPrevailingConditions(parameters.getRequest())) {
|
||||
parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT)
|
||||
}
|
||||
}
|
||||
return logoutRequestResolver
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Then, you can supply your custom `Saml2LogoutResponseResolver` in the DSL as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
http
|
||||
.saml2Logout((saml2) -> saml2
|
||||
@@ -483,30 +199,12 @@ http
|
||||
);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
saml2Logout {
|
||||
logoutRequest {
|
||||
logoutRequestResolver = this.logoutRequestResolver
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
== Customizing `<saml2:LogoutRequest>` Authentication
|
||||
|
||||
To customize validation, you can implement your own `Saml2LogoutRequestValidator`.
|
||||
At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutRequestValidator` like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
@Component
|
||||
public class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator {
|
||||
@@ -523,66 +221,24 @@ public class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValid
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
open class MyOpenSamlLogoutRequestValidator: Saml2LogoutRequestValidator {
|
||||
private val delegate = OpenSamlLogoutRequestValidator()
|
||||
|
||||
@Override
|
||||
fun logout(parameters: Saml2LogoutRequestValidatorParameters): Saml2LogoutRequestValidator {
|
||||
// verify signature, issuer, destination, and principal name
|
||||
val result = delegate.authenticate(authentication)
|
||||
|
||||
val logoutRequest: LogoutRequest = // ... parse using OpenSAML
|
||||
// perform custom validation
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Then, you can supply your custom `Saml2LogoutRequestValidator` in the DSL as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
http
|
||||
.saml2Logout((saml2) -> saml2
|
||||
.logoutRequest((request) -> request
|
||||
.logoutRequestValidator(myOpenSamlLogoutRequestValidator)
|
||||
.logoutRequestAuthenticator(myOpenSamlLogoutRequestAuthenticator)
|
||||
)
|
||||
);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
saml2Logout {
|
||||
logoutRequest {
|
||||
logoutRequestValidator = myOpenSamlLogoutRequestValidator
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
== Customizing `<saml2:LogoutResponse>` Authentication
|
||||
|
||||
To customize validation, you can implement your own `Saml2LogoutResponseValidator`.
|
||||
At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutResponseValidator` like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
@Component
|
||||
public class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidator {
|
||||
@@ -599,33 +255,9 @@ public class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseVal
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
open class MyOpenSamlLogoutResponseValidator: Saml2LogoutResponseValidator {
|
||||
private val delegate = OpenSamlLogoutResponseValidator()
|
||||
|
||||
@Override
|
||||
fun logout(parameters: Saml2LogoutResponseValidatorParameters): Saml2LogoutResponseValidator {
|
||||
// verify signature, issuer, destination, and status
|
||||
val result = delegate.authenticate(authentication)
|
||||
|
||||
val logoutResponse: LogoutResponse = // ... parse using OpenSAML
|
||||
// perform custom validation
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Then, you can supply your custom `Saml2LogoutResponseValidator` in the DSL as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
http
|
||||
.saml2Logout((saml2) -> saml2
|
||||
@@ -635,31 +267,13 @@ http
|
||||
);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
saml2Logout {
|
||||
logoutResponse {
|
||||
logoutResponseValidator = myOpenSamlLogoutResponseValidator
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
== Customizing `<saml2:LogoutRequest>` storage
|
||||
|
||||
When your application sends a `<saml2:LogoutRequest>`, the value is stored in the session so that the `RelayState` parameter and the `InResponseTo` attribute in the `<saml2:LogoutResponse>` can be verified.
|
||||
|
||||
If you want to store logout requests in some place other than the session, you can supply your custom implementation in the DSL, like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
http
|
||||
.saml2Logout((saml2) -> saml2
|
||||
@@ -668,24 +282,3 @@ http
|
||||
)
|
||||
);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
saml2Logout {
|
||||
logoutRequest {
|
||||
logoutRequestRepository = myCustomLogoutRequestRepository
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[jc-logout-references]]
|
||||
== Further Logout-Related References
|
||||
|
||||
- xref:servlet/test/mockmvc/logout.adoc#test-logout[Testing Logout]
|
||||
- xref:servlet/integrations/servlet-api.adoc#servletapi-logout[HttpServletRequest.logout()]
|
||||
- xref:servlet/exploits/csrf.adoc#csrf-considerations-logout[Logging Out] in section CSRF Caveats
|
||||
|
||||
@@ -1,306 +1,30 @@
|
||||
[[new]]
|
||||
= What's New in Spring Security 6.3
|
||||
= What's New in Spring Security 6.2
|
||||
|
||||
Spring Security 6.3 provides a number of new features.
|
||||
Below are the highlights of the release, or you can view https://github.com/spring-projects/spring-security/releases[the release notes] for a detailed listing of each feature and bug fix.
|
||||
Spring Security 6.2 provides a number of new features.
|
||||
Below are the highlights of the release.
|
||||
|
||||
== Passive JDK Serialization Support
|
||||
== Configuration
|
||||
|
||||
When it comes to its support for JDK-serialized security components, Spring Security has historically been quite aggressive, supporting each serialization version for only one Spring Security minor version.
|
||||
This meant that if you had JDK-serialized security components, then they would need to be evacuated before upgrading to the next Spring Security version since they would no longer be deserializable.
|
||||
* https://github.com/spring-projects/spring-security/issues/5011[gh-5011] - xref:servlet/integrations/cors.adoc[(docs)] Automatically enable `.cors()` if `CorsConfigurationSource` bean is present
|
||||
* https://github.com/spring-projects/spring-security/issues/13204[gh-13204] - xref:migration-7/configuration.adoc#_use_with_instead_of_apply_for_custom_dsls[(docs)] Add `AbstractConfiguredSecurityBuilder.with(...)` method to apply configurers returning the builder
|
||||
* https://github.com/spring-projects/spring-security/pull/13587[gh-13587] - https://spring.io/blog/2023/08/22/tackling-the-oauth2-client-component-model-in-spring-security/[blog post] Simplify configuration of OAuth2 Client component model
|
||||
* https://github.com/spring-projects/spring-security/issues/13666[gh-13666], https://github.com/spring-projects/spring-security/pull/13667[gh-13667], https://github.com/spring-projects/spring-security/issues/13726[gh-13726], https://github.com/spring-projects/spring-security/issues/13850[gh-13850] - xref:servlet/authorization/authorize-http-requests.adoc#match-by-mvc[docs] Improved CVE-2023-34035 detection
|
||||
|
||||
Now that Spring Security performs a minor release every six months, this became a much larger pain point.
|
||||
To address that, Spring Security now will https://spring.io/blog/2024/01/19/spring-security-6-3-adds-passive-jdk-serialization-deserialization-for[maintain passivity with JDK serialization], like it does with JSON serialization, making for more seamless upgrades.
|
||||
== OAuth 2.0/OIDC
|
||||
|
||||
== Authorization
|
||||
* https://github.com/spring-projects/spring-security/issues/7845[gh-7845] - xref:reactive/oauth2/login/logout.adoc#configure-provider-initiated-oidc-logout[docs] Add OIDC Back-channel Logout Support
|
||||
|
||||
An ongoing theme for the last several releases has been to refactor and improve Spring Security's authorization subsystem.
|
||||
Starting with replacing the `AccessDecisionManager` API with `AuthorizationManager` it's now come to the point where we are able to add several exciting new features.
|
||||
== Messaging
|
||||
|
||||
=== Annotation Parameters - https://github.com/spring-projects/spring-security/issues/14480[#14480]
|
||||
* https://github.com/spring-projects/spring-security/pull/12532[gh-12532] - Add Security Context Propagation Support
|
||||
|
||||
The first 6.3 feature is https://github.com/spring-projects/spring-security/issues/14480[support for annotation parameters].
|
||||
Consider Spring Security's support for xref:servlet/authorization/method-security.adoc#meta-annotations[meta-annotations] like this one:
|
||||
== Web
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@PreAuthorize("hasAuthority('SCOPE_message:read')")
|
||||
public @interface HasMessageRead {}
|
||||
----
|
||||
* https://github.com/spring-projects/spring-security/pull/12817[gh-12817] - Make Configurable RedirectStrategy status code
|
||||
* https://github.com/spring-projects/spring-security/issues/13988[gh-13988] - Make Configurable HTTP Basic request parsing
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@PreAuthorize("hasAuthority('SCOPE_message:read')")
|
||||
annotation class HasMessageRead
|
||||
----
|
||||
======
|
||||
== Documentation
|
||||
|
||||
Before this release, something like this is only helpful when it is used widely across the codebase.
|
||||
But now, xref:servlet/authorization/method-security.adoc#_templating_meta_annotation_expressions[you can add parameters] like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@PreAuthorize("hasAuthority('SCOPE_{scope}')")
|
||||
public @interface HasScope {
|
||||
String scope();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@PreAuthorize("hasAuthority('SCOPE_{scope}')")
|
||||
annotation class HasScope (val scope:String)
|
||||
----
|
||||
======
|
||||
|
||||
making it possible to do things like this:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@HasScope("message:read")
|
||||
public String method() { ... }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@HasScope("message:read")
|
||||
fun method(): String { ... }
|
||||
----
|
||||
======
|
||||
|
||||
and apply your SpEL expression in several more places.
|
||||
|
||||
=== Secure Return Values - https://github.com/spring-projects/spring-security/issues/14596[#14596], https://github.com/spring-projects/spring-security/issues/14597[#14597]
|
||||
|
||||
Since the early days of Spring Security, you've been able to xref:servlet/authorization/method-security.adoc#use-preauthorize[annotate Spring beans with `@PreAuthorize` and `@PostAuthorize`].
|
||||
But controllers, services, and repositories are not the only things you care to secure.
|
||||
For example, what about a domain object `Order` where only admins should be able to call the `Order#getPayment` method?
|
||||
|
||||
Now in 6.3, https://github.com/spring-projects/spring-security/issues/14597[you can annotate those methods], too.
|
||||
First, annotate the `getPayment` method like you would a Spring bean:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class Order {
|
||||
|
||||
@HasScope("payment:read")
|
||||
Payment getPayment() { ... }
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class Order {
|
||||
|
||||
@HasScope("payment:read")
|
||||
fun getPayment(): Payment { ... }
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
And then xref:servlet/authorization/method-security.adoc#authorize-object[annotate your Spring Data repository with `@AuthorizeReturnObject`] like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public interface OrderRepository implements CrudRepository<Order, String> {
|
||||
|
||||
@AuthorizeReturnObject
|
||||
Optional<Order> findOrderById(String id);
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
|
||||
interface OrderRepository : CrudRepository<Order, String> {
|
||||
@AuthorizeReturnObject
|
||||
fun findOrderById(id: String?): Optional<Order?>?
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
At that point, Spring Security will protect any `Order` returned from `findOrderById` by way of https://github.com/spring-projects/spring-security/issues/14596[proxying the `Order` instance].
|
||||
|
||||
=== Error Handling - https://github.com/spring-projects/spring-security/issues/14598[#14598], https://github.com/spring-projects/spring-security/issues/14600[#14600], https://github.com/spring-projects/spring-security/issues/14601[#14601]
|
||||
|
||||
In this release, you can also https://github.com/spring-projects/spring-security/issues/14601[intercept and handle failure at the method level] with its last new method security annotation.
|
||||
|
||||
When you xref:servlet/authorization/method-security.adoc#fallback-values-authorization-denied[annotate a method with `@HandleAuthorizationDenied`] like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class Payment {
|
||||
@HandleAuthorizationDenied(handlerClass=Mask.class)
|
||||
@PreAuthorize("hasAuthority('card:read')")
|
||||
public String getCreditCardNumber() { ... }
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class Payment {
|
||||
@HandleAuthorizationDenied(handlerClass=Mask.class)
|
||||
@PreAuthorize("hasAuthority('card:read')")
|
||||
fun getCreditCardNumber(): String { ... }
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
and publish a `Mask` bean:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class Mask implements MethodAuthorizationDeniedHandler {
|
||||
@Override
|
||||
public Object handleDeniedInvocation(MethodInvocation invocation, AuthorizationResult result) {
|
||||
return "***";
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class Mask : MethodAuthorizationDeniedHandler {
|
||||
fun handleDeniedInvocation(invocation: MethodInvocation?, result: AuthorizationResult?): Any = "***"
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
then any unauthorized call to `Payment#getCreditCardNumber` will return `\***` instead of the number.
|
||||
|
||||
You can see all these features at work together in https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/data[the latest Spring Security Data sample].
|
||||
|
||||
== Compromised Password Checking - https://github.com/spring-projects/spring-security/issues/7395[#7395]
|
||||
|
||||
If you are going to let users pick passwords, it's critical to ensure that such a password isn't already compromised.
|
||||
Spring Security 6.3 makes this as simple as xref:features/authentication/password-storage.adoc#authentication-compromised-password-check[publishing a `CompromisedPasswordChecker` bean]:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public CompromisedPasswordChecker compromisedPasswordChecker() {
|
||||
return new HaveIBeenPwnedRestApiPasswordChecker();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun compromisedPasswordChecker(): CompromisedPasswordChecker = HaveIBeenPwnedRestApiPasswordChecker()
|
||||
----
|
||||
======
|
||||
|
||||
== `spring-security-rsa` is now part of Spring Security - https://github.com/spring-projects/spring-security/issues/14202[#14202]
|
||||
|
||||
Since 2017, Spring Security has been undergoing a long-standing initiative to fold various Spring Security extensions into Spring Security proper.
|
||||
In 6.3, `spring-security-rsa` becomes the latest of these projects which will help the team maintain and add features to it, long-term.
|
||||
|
||||
`spring-security-rsa` provides a number of https://github.com/spring-projects/spring-security/blob/main/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java[handy `BytesEncryptor`] https://github.com/spring-projects/spring-security/blob/main/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java[implementations] as well as https://github.com/spring-projects/spring-security/blob/main/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java[a simpler API for working with ``KeyStore``s].
|
||||
|
||||
|
||||
== OAuth 2.0 Token Exchange Grant - https://github.com/spring-projects/spring-security/issues/5199[#5199]
|
||||
|
||||
One of https://github.com/spring-projects/spring-security/issues/5199[the most highly-voted OAuth 2.0 features] in Spring Security is now in place in 6.3, which is the support for https://datatracker.ietf.org/doc/html/rfc8693#section-2[the OAuth 2.0 Token Exchange grant].
|
||||
|
||||
For xref:servlet/oauth2/client/authorization-grants.adoc#token-exchange-grant-access-token[any client configured for token exchange], you can activate it in Spring Security by adding a `TokenExchangeAuthorizedClientProvider` instance to your `OAuth2AuthorizedClientManager` like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2AuthorizedClientProvider tokenExchange() {
|
||||
return new TokenExchangeOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun tokenExchange(): OAuth2AuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
|
||||
----
|
||||
======
|
||||
|
||||
and then xref:servlet/oauth2/client/authorized-clients.adoc#oauth2Client-registered-authorized-client[use the `@RegisteredOAuth2AuthorizedClient` annotation] as per usual to retrieve the appropriate token with the expanded privileges your resource server needs.
|
||||
|
||||
== Additional Highlights
|
||||
|
||||
- https://github.com/spring-projects/spring-security/pull/14655[gh-14655] - Add `DelegatingAuthenticationConverter`
|
||||
- https://github.com/spring-projects/spring-security/issues/6192[gh-6192] - Add Concurrent Sessions Control on WebFlux (xref:reactive/authentication/concurrent-sessions-control.adoc[docs])
|
||||
- https://github.com/spring-projects/spring-security/pull/14193[gh-14193] - Added support for CAS Gateway Authentication
|
||||
- https://github.com/spring-projects/spring-security/issues/13259[gh-13259] - Customize when UserInfo is called
|
||||
- https://github.com/spring-projects/spring-security/pull/14168[gh-14168] - Introduce Customizable AuthorizationFailureHandler in OAuth2AuthorizationRequestRedirectFilter
|
||||
- https://github.com/spring-projects/spring-security/issues/14672[gh-14672] - Customize mapping the OidcUser from OidcUserRequest and OidcUserInfo
|
||||
- https://github.com/spring-projects/spring-security/issues/13763[gh-13763] - Simplify configuration of reactive OAuth2 Client component model
|
||||
- https://github.com/spring-projects/spring-security/issues/14758[gh-14758] - Update reactive OAuth2 docs landing page with examples (xref:reactive/oauth2/index.adoc[docs])
|
||||
- https://github.com/spring-projects/spring-security/issues/10538[gh-10538] - Support Certificate-Bound JWT Access Token Validation
|
||||
- https://github.com/spring-projects/spring-security/pull/14265[gh-14265] - Support Nested username in UserInfo response
|
||||
- https://github.com/spring-projects/spring-security/pull/14265[gh-14449] - Add `SecurityContext` argument resolver
|
||||
|
||||
And for an exhaustive list, please see the release notes for https://github.com/spring-projects/spring-security/releases/tag/6.3.0-RC1[6.3.0-RC1], https://github.com/spring-projects/spring-security/releases/tag/6.3.0-M3[6.3.0-M3], https://github.com/spring-projects/spring-security/releases/tag/6.3.0-M2[6.3.0-M2], and https://github.com/spring-projects/spring-security/releases/tag/6.3.0-M1[6.3.0-M1].
|
||||
* https://github.com/spring-projects/spring-security/issues/13784[gh-13784] - xref:servlet/oauth2/index.adoc[docs] - Update OAuth2 docs landing page with examples
|
||||
* https://github.com/spring-projects/spring-security/issues/11926[gh-11926] - xref:servlet/authentication/passwords/index.adoc#publish-authentication-manager-bean[docs] Document how to publish an `AuthenticationManager` `@Bean` without `WebSecurityConfigurerAdapter`
|
||||
|
||||
Reference in New Issue
Block a user