parent
3d0be9beba
commit
7675874137
|
@ -2,7 +2,16 @@
|
||||||
* xref:prerequisites.adoc[Prerequisites]
|
* xref:prerequisites.adoc[Prerequisites]
|
||||||
* xref:community.adoc[Community]
|
* xref:community.adoc[Community]
|
||||||
* xref:whats-new.adoc[What's New]
|
* xref:whats-new.adoc[What's New]
|
||||||
* xref:migration.adoc[Migrating for 6.0]
|
* xref:migration/index.adoc[Migrating for 6.0]
|
||||||
|
** xref:migration/servlet/index.adoc[Servlet Migrations]
|
||||||
|
*** xref:migration/servlet/session-management.adoc[Session Management]
|
||||||
|
*** xref:migration/servlet/exploits.adoc[Exploit Protection]
|
||||||
|
*** xref:migration/servlet/config.adoc[Configuration]
|
||||||
|
*** xref:migration/servlet/authentication.adoc[Authentication]
|
||||||
|
*** xref:migration/servlet/authorization.adoc[Authorization]
|
||||||
|
*** xref:migration/servlet/oauth2.adoc[OAuth]
|
||||||
|
*** xref:migration/servlet/saml2.adoc[SAML]
|
||||||
|
** xref:migration/reactive.adoc[Reactive Migrations]
|
||||||
* xref:getting-spring-security.adoc[Getting Spring Security]
|
* xref:getting-spring-security.adoc[Getting Spring Security]
|
||||||
* xref:features/index.adoc[Features]
|
* xref:features/index.adoc[Features]
|
||||||
** xref:features/authentication/index.adoc[Authentication]
|
** xref:features/authentication/index.adoc[Authentication]
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,364 @@
|
||||||
|
[[migration]]
|
||||||
|
= Migrating to 6.0
|
||||||
|
|
||||||
|
The Spring Security team has prepared the 5.8 release to simplify upgrading to Spring Security 6.0.
|
||||||
|
Use 5.8 and the steps below to minimize changes when
|
||||||
|
ifdef::spring-security-version[]
|
||||||
|
xref:6.0.0@migration.adoc[updating to 6.0]
|
||||||
|
endif::[]
|
||||||
|
ifndef::spring-security-version[]
|
||||||
|
updating to 6.0
|
||||||
|
endif::[]
|
||||||
|
.
|
||||||
|
|
||||||
|
== Update to Spring Security 5.8
|
||||||
|
|
||||||
|
The first step is to ensure you are the latest patch release of Spring Boot 2.7.
|
||||||
|
Next, you should ensure you are on the latest patch release of Spring Security 5.8.
|
||||||
|
If you are using Spring Boot, you will need to override the Spring Boot version from Spring Security 5.7 to 5.8.
|
||||||
|
Spring Security 5.8 is fully compatible with Spring Security 5.7 and thus Spring Boot 2.7.
|
||||||
|
For directions, on how to update to Spring Security 5.8 visit the xref:getting-spring-security.adoc[] section of the reference guide.
|
||||||
|
|
||||||
|
== Update Password Encoding
|
||||||
|
|
||||||
|
In 6.0, password encoding minimums are updated for PBKDF2, SCrypt, and Argon2.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
If you are using the default password encoder, then there are no preparation steps to follow and this section can be skipped.
|
||||||
|
====
|
||||||
|
|
||||||
|
=== Update `Pbkdf2PasswordEncoder`
|
||||||
|
|
||||||
|
If you are xref:features/authentication/password-storage.adoc#authentication-password-storage-pbkdf2[using `Pbkdf2PasswordEncoder`], the constructors are replaced with static factories that refer to the Spring Security version that the given settings apply to.
|
||||||
|
|
||||||
|
==== Replace Deprecated Constructor Usage
|
||||||
|
|
||||||
|
If you use the default constructor, you should begin by changing:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return new Pbkdf2PasswordEncoder();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
return Pbkdf2PasswordEncoder()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
return Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
Or, if you have custom settings, change to the constructor that specifies all settings, like so:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
PasswordEncoder current = new Pbkdf2PasswordEncoder("mysecret".getBytes(UTF_8), 320000);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
val current: PasswordEncoder = Pbkdf2PasswordEncoder("mysecret".getBytes(UTF_8), 320000)
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
Change them to use the fully-specified constructor, like the following:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
PasswordEncoder current = new Pbkdf2PasswordEncoder("mysecret".getBytes(UTF_8), 16, 185000, 256);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
val current: PasswordEncoder = Pbkdf2PasswordEncoder("mysecret".getBytes(UTF_8), 16, 185000, 256)
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
==== Use `DelegatedPasswordEncoder`
|
||||||
|
|
||||||
|
Once you are not using the deprecated constructor, the next step is to prepare your code to upgrade to the latest standards by using `DelegatedPasswordEncoder`.
|
||||||
|
The following code configures the delegating encoder to detect passwords that are using `current` and replace them with the latest:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
String prefix = "pbkdf2@5.8";
|
||||||
|
PasswordEncoder current = // ... see previous step
|
||||||
|
PasswordEncoder upgraded = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
|
||||||
|
DelegatedPasswordEncoder delegating = new DelegatedPasswordEncoder(prefix, Map.of(prefix, upgraded));
|
||||||
|
delegating.setDefaultPasswordEncoderFormatches(current);
|
||||||
|
return delegating;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
String prefix = "pbkdf2@5.8"
|
||||||
|
PasswordEncoder current = // ... see previous step
|
||||||
|
PasswordEncoder upgraded = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
|
||||||
|
DelegatedPasswordEncoder delegating = new DelegatedPasswordEncoder(prefix, Map.of(prefix, upgraded))
|
||||||
|
delegating.setDefaultPasswordEncoderFormatches(current)
|
||||||
|
return delegating
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
=== Update `SCryptPasswordEncoder`
|
||||||
|
|
||||||
|
If you are xref:features/authentication/password-storage.adoc#authentication-password-storage-scrypt[using `SCryptPasswordEncoder`], the constructors are replaced with static factories that refer to the Spring Security version that the given settings apply to.
|
||||||
|
|
||||||
|
==== Replace Deprecated Constructor Usage
|
||||||
|
|
||||||
|
If you use the default constructor, you should begin by changing:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return new SCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
return SCryptPasswordEncoder()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
return SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
==== Use `DelegatedPasswordEncoder`
|
||||||
|
|
||||||
|
Once you are not using the deprecated constructor, the next step is to prepare your code to upgrade to the latest standards by using `DelegatedPasswordEncoder`.
|
||||||
|
The following code configures the delegating encoder to detect passwords that are using `current` and replace them with the latest:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
String prefix = "scrypt@5.8";
|
||||||
|
PasswordEncoder current = // ... see previous step
|
||||||
|
PasswordEncoder upgraded = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
|
||||||
|
DelegatedPasswordEncoder delegating = new DelegatedPasswordEncoder(prefix, Map.of(prefix, upgraded));
|
||||||
|
delegating.setDefaultPasswordEncoderFormatches(current);
|
||||||
|
return delegating;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
String prefix = "scrypt@5.8"
|
||||||
|
PasswordEncoder current = // ... see previous step
|
||||||
|
PasswordEncoder upgraded = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
|
||||||
|
DelegatedPasswordEncoder delegating = new DelegatedPasswordEncoder(prefix, Map.of(prefix, upgraded))
|
||||||
|
delegating.setDefaultPasswordEncoderFormatches(current)
|
||||||
|
return delegating
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
=== Update `Argon2PasswordEncoder`
|
||||||
|
|
||||||
|
If you are xref:features/authentication/password-storage.adoc#authentication-password-storage-argon2[using `Argon2PasswordEncoder`], the constructors are replaced with static factories that refer to the Spring Security version that the given settings apply to.
|
||||||
|
|
||||||
|
==== Replace Deprecated Constructor Usage
|
||||||
|
|
||||||
|
If you use the default constructor, you should begin by changing:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return new Argon2PasswordEncoder();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
return Argon2PasswordEncoder()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
==== Use `DelegatedPasswordEncoder`
|
||||||
|
|
||||||
|
Once you are not using the deprecated constructor, the next step is to prepare your code to upgrade to the latest standards by using `DelegatedPasswordEncoder`.
|
||||||
|
The following code configures the delegating encoder to detect passwords that are using `current` and replace them with the latest:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
String prefix = "argon@5.8";
|
||||||
|
PasswordEncoder current = // ... see previous step
|
||||||
|
PasswordEncoder upgraded = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
|
||||||
|
DelegatedPasswordEncoder delegating = new DelegatedPasswordEncoder(prefix, Map.of(prefix, upgraded));
|
||||||
|
delegating.setDefaultPasswordEncoderFormatches(current);
|
||||||
|
return delegating;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun passwordEncoder(): PasswordEncoder {
|
||||||
|
String prefix = "argon@5.8"
|
||||||
|
PasswordEncoder current = // ... see previous step
|
||||||
|
PasswordEncoder upgraded = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
|
||||||
|
DelegatedPasswordEncoder delegating = new DelegatedPasswordEncoder(prefix, Map.of(prefix, upgraded))
|
||||||
|
delegating.setDefaultPasswordEncoderFormatches(current)
|
||||||
|
return delegating
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
== Stop using `Encryptors.queryableText`
|
||||||
|
|
||||||
|
`Encryptors.queryableText(CharSequence,CharSequence)` is unsafe since https://tanzu.vmware.com/security/cve-2020-5408[the same input data will produce the same output].
|
||||||
|
It was deprecated and will be removed in 6.0; Spring Security no longer supports encrypting data in this way.
|
||||||
|
|
||||||
|
To upgrade, you will either need to re-encrypt with a supported mechanism or store it decrypted.
|
||||||
|
|
||||||
|
Consider the following pseudocode for reading each encrypted entry from a table, decrypting it, and then re-encrypting it using a supported mechanism:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
TextEncryptor deprecated = Encryptors.queryableText(password, salt);
|
||||||
|
BytesEncryptor aes = new AesBytesEncryptor(password, salt, KeyGenerators.secureRandom(12), CipherAlgorithm.GCM);
|
||||||
|
TextEncryptor supported = new HexEncodingTextEncryptor(aes);
|
||||||
|
for (MyEntry entry : entries) {
|
||||||
|
String value = deprecated.decrypt(entry.getEncryptedValue()); <1>
|
||||||
|
entry.setEncryptedValue(supported.encrypt(value)); <2>
|
||||||
|
entryService.save(entry)
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
<1> - The above uses the deprecated `queryableText` to convert the value to plaintext.
|
||||||
|
<2> - Then, the value is re-encrypted with a supported Spring Security mechanism.
|
||||||
|
|
||||||
|
Please see the reference manual for more information on what xref:features/integrations/cryptography.adoc[encryption mechanisms Spring Security supports].
|
||||||
|
|
||||||
|
== Perform Application-Specific Steps
|
||||||
|
|
||||||
|
Next, there are steps you need to perform based on whether it is a xref:migration/servlet/index.adoc[Servlet] or xref:migration/reactive.adoc[Reactive] application.
|
|
@ -0,0 +1,197 @@
|
||||||
|
= Reactive Migrations
|
||||||
|
|
||||||
|
If you have already performed the xref:migration/index.adoc[initial migration steps] for your Reactive application, you're now ready to perform steps specific to Reactive applications.
|
||||||
|
|
||||||
|
== Use `AuthorizationManager` for Method Security
|
||||||
|
|
||||||
|
xref:reactive/authorization/method.adoc[Method Security] has been xref:reactive/authorization/method.adoc#jc-enable-reactive-method-security-authorization-manager[improved] through {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[the `AuthorizationManager` API] and direct use of Spring AOP.
|
||||||
|
|
||||||
|
Should you run into trouble with making these changes, you can follow the
|
||||||
|
<<reactive-authorizationmanager-methods-opt-out,opt out steps>> at the end of this section.
|
||||||
|
|
||||||
|
In Spring Security 5.8, `useAuthorizationManager` was added to {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurity.html[`@EnableReactiveMethodSecurity`] to allow applications to opt in to ``AuthorizationManager``'s features.
|
||||||
|
|
||||||
|
[[reactive-change-to-useauthorizationmanager]]
|
||||||
|
=== Change `useAuthorizationManager` to `true`
|
||||||
|
|
||||||
|
To opt in, change `useAuthorizationManager` to `true` like so:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@EnableReactiveMethodSecurity
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@EnableReactiveMethodSecurity
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
changes to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@EnableReactiveMethodSecurity(useAuthorizationManager = true)
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@EnableReactiveMethodSecurity(useAuthorizationManager = true)
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[reactive-check-for-annotationconfigurationexceptions]]
|
||||||
|
=== Check for ``AnnotationConfigurationException``s
|
||||||
|
|
||||||
|
`useAuthorizationManager` activates stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
|
||||||
|
If after turning on `useAuthorizationManager` you see ``AnnotationConfigurationException``s in your logs, follow the instructions in the exception message to clean up your application's method security annotation usage.
|
||||||
|
|
||||||
|
[[reactive-authorizationmanager-methods-opt-out]]
|
||||||
|
=== Opt-out Steps
|
||||||
|
|
||||||
|
If you ran into trouble with `AuthorizationManager` for reactive method security, you can opt out by changing:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@EnableReactiveMethodSecurity
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@EnableReactiveMethodSecurity
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@EnableReactiveMethodSecurity(useAuthorizationManager = false)
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@EnableReactiveMethodSecurity(useAuthorizationManager = false)
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
== Propagate ``AuthenticationServiceException``s
|
||||||
|
|
||||||
|
{security-api-url}org/springframework/security/web/server/Webauthentication/AuthenticationWebFilter.html[`AuthenticationFilter`] propagates {security-api-url}org/springframework/security/authentication/AuthenticationServiceException.html[``AuthenticationServiceException``]s to the {security-api-url}org/springframework/security/web/server/ServerAuthenticationEntryPoint.html[`ServerAuthenticationEntryPoint`].
|
||||||
|
Because ``AuthenticationServiceException``s represent a server-side error instead of a client-side error, in 6.0, this changes to propagate them to the container.
|
||||||
|
|
||||||
|
=== Configure `ServerAuthenticationFailureHandler` to rethrow ``AuthenticationServiceException``s
|
||||||
|
|
||||||
|
To prepare for the 6.0 default, `httpBasic` and `oauth2ResourceServer` should be configured to rethrow ``AuthenticationServiceException``s.
|
||||||
|
|
||||||
|
For each, construct the appropriate authentication entry point for `httpBasic` and for `oauth2ResourceServer`:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
ServerAuthenticationEntryPoint bearerEntryPoint = new BearerTokenServerAuthenticationEntryPoint();
|
||||||
|
ServerAuthenticationEntryPoint basicEntryPoint = new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED);
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
val bearerEntryPoint: ServerAuthenticationEntryPoint = BearerTokenServerAuthenticationEntryPoint()
|
||||||
|
val basicEntryPoint: ServerAuthenticationEntryPoint = HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
If you use a custom `AuthenticationEntryPoint` for either or both mechanisms, use that one instead for the remaining steps.
|
||||||
|
====
|
||||||
|
|
||||||
|
Then, construct and configure a `ServerAuthenticationEntryPointFailureHandler` for each one:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
AuthenticationFailureHandler bearerFailureHandler = new ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint);
|
||||||
|
bearerFailureHandler.setRethrowAuthenticationServiceException(true);
|
||||||
|
AuthenticationFailureHandler basicFailureHandler = new ServerAuthenticationEntryPointFailureHandler(basicEntryPoint);
|
||||||
|
basicFailureHandler.setRethrowAuthenticationServiceException(true)
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
val bearerFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint)
|
||||||
|
bearerFailureHandler.setRethrowAuthenticationServiceException(true)
|
||||||
|
val basicFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(basicEntryPoint)
|
||||||
|
basicFailureHandler.setRethrowAuthenticationServiceException(true)
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
Finally, wire each authentication failure handler into the DSL, like so:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
http
|
||||||
|
.httpBasic((basic) -> basic.authenticationFailureHandler(basicFailureHandler))
|
||||||
|
.oauth2ResourceServer((oauth2) -> oauth2.authenticationFailureHandler(bearerFailureHandler))
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
http {
|
||||||
|
httpBasic {
|
||||||
|
authenticationFailureHandler = basicFailureHandler
|
||||||
|
}
|
||||||
|
oauth2ResourceServer {
|
||||||
|
authenticationFailureHandler = bearerFailureHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[reactive-authenticationfailurehandler-opt-out]]
|
||||||
|
=== Opt-out Steps
|
||||||
|
|
||||||
|
To opt-out of the 6.0 defaults and instead continue to pass `AuthenticationServiceException` on to ``ServerAuthenticationEntryPoint``s, you can follow the same steps as above, except set `rethrowAuthenticationServiceException` to false.
|
||||||
|
|
||||||
|
== Deprecations in OAuth2 Client
|
||||||
|
|
||||||
|
=== `ServerOAuth2AuthorizedClientExchangeFilterFunction`
|
||||||
|
|
||||||
|
The method `setAccessTokenExpiresSkew(...)` can be replaced with one of:
|
||||||
|
|
||||||
|
* `ClientCredentialsReactiveOAuth2AuthorizedClientProvider#setClockSkew(...)`
|
||||||
|
* `RefreshTokenReactiveOAuth2AuthorizedClientProvider#setClockSkew(...)`
|
||||||
|
* `JwtBearerReactiveOAuth2AuthorizedClientProvider#setClockSkew(...)`
|
||||||
|
|
||||||
|
The method `setClientCredentialsTokenResponseClient(...)` can be replaced with the constructor `ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveOAuth2AuthorizedClientManager)`.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
See xref:reactive/oauth2/client/authorization-grants.adoc#oauth2Client-client-creds-grant[Client Credentials] for more information.
|
||||||
|
====
|
||||||
|
|
||||||
|
=== `WebSessionOAuth2ServerAuthorizationRequestRepository`
|
||||||
|
|
||||||
|
The method `setAllowMultipleAuthorizationRequests(...)` has no direct replacement.
|
||||||
|
|
||||||
|
=== `UnAuthenticatedServerOAuth2AuthorizedClientRepository`
|
||||||
|
|
||||||
|
The class `UnAuthenticatedServerOAuth2AuthorizedClientRepository` has no direct replacement. Usage of the class can be replaced with `AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager`.
|
|
@ -0,0 +1,244 @@
|
||||||
|
= Authentication Migrations
|
||||||
|
|
||||||
|
The following steps relate to changes around how authentication is performed.
|
||||||
|
|
||||||
|
[[servlet-opt-in-sha256-rememberme]]
|
||||||
|
== Use SHA-256 in Remember Me
|
||||||
|
|
||||||
|
The `TokenBasedRememberMeServices` implementation now supports SHA-256 for the Remember Me token and this is the default in Spring Security 6.
|
||||||
|
This change makes the implementation more secure by default since MD5 is already proven to be a weak hashing algorithm and vulnerable against collision attacks and modular differential attacks.
|
||||||
|
|
||||||
|
The new generated tokens now have the information of which algorithm was used to generate the token and that information is used in order to match it.
|
||||||
|
If the algorithm name is not present, then the `matchingAlgorithm` property is used to check the token.
|
||||||
|
This allows for a smooth transition from MD5 to SHA-256.
|
||||||
|
|
||||||
|
To opt into the new Spring Security 6 default to encode the tokens while still being able to decode tokens encoded with MD5, you can set the `encodingAlgorithm` property to SHA-256 and the `matchingAlgorithm` property to MD5.
|
||||||
|
See the xref:servlet/authentication/rememberme.adoc#_tokenbasedremembermeservices[reference documentation] and the {security-api-url}org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.html[API docs] for more information.
|
||||||
|
|
||||||
|
[[servlet-opt-in-sha256-sha256-encoding]]
|
||||||
|
.Use Spring Security 6 defaults for encoding, SHA-256 for encoding and MD5 for matching
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.rememberMe((remember) -> remember
|
||||||
|
.rememberMeServices(rememberMeServices)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
|
||||||
|
RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.SHA256;
|
||||||
|
TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm);
|
||||||
|
rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
|
||||||
|
return rememberMe;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http>
|
||||||
|
<remember-me services-ref="rememberMeServices"/>
|
||||||
|
</http>
|
||||||
|
|
||||||
|
<bean id="rememberMeServices" class=
|
||||||
|
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
|
||||||
|
<property name="userDetailsService" ref="myUserDetailsService"/>
|
||||||
|
<property name="key" value="springRocks"/>
|
||||||
|
<property name="matchingAlgorithm" value="MD5"/>
|
||||||
|
<property name="encodingAlgorithm" value="SHA256"/>
|
||||||
|
</bean>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
At some point, you will want to fully migrate to Spring Security 6 defaults. But how do you know when it is safe to do so?
|
||||||
|
Let's suppose that you deployed your application using SHA-256 as the encoding algorithm (as you have done <<servlet-opt-in-sha256-sha256-encoding,here>>) on November 1st, if you have the value for the `tokenValiditySeconds` property set to N days (14 is the default), you can migrate to SHA-256 N days after November 1st (which is November 15th in this example).
|
||||||
|
By that time, all the tokens generated with MD5 will have expired.
|
||||||
|
|
||||||
|
.Use Spring Security 6 defaults, SHA-256 for both encoding and matching
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.rememberMe((remember) -> remember
|
||||||
|
.rememberMeServices(rememberMeServices)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
|
||||||
|
RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.SHA256;
|
||||||
|
TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm);
|
||||||
|
rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.SHA256);
|
||||||
|
return rememberMe;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http>
|
||||||
|
<remember-me services-ref="rememberMeServices"/>
|
||||||
|
</http>
|
||||||
|
|
||||||
|
<bean id="rememberMeServices" class=
|
||||||
|
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
|
||||||
|
<property name="userDetailsService" ref="myUserDetailsService"/>
|
||||||
|
<property name="key" value="springRocks"/>
|
||||||
|
<property name="matchingAlgorithm" value="SHA256"/>
|
||||||
|
<property name="encodingAlgorithm" value="SHA256"/>
|
||||||
|
</bean>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If you are having problems with the Spring Security 6 defaults, you can explicitly opt into 5.8 defaults using the following configuration:
|
||||||
|
|
||||||
|
.Use MD5 for both encoding and matching algorithms
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.rememberMe((remember) -> remember
|
||||||
|
.rememberMeServices(rememberMeServices)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
|
||||||
|
RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.MD5;
|
||||||
|
TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm);
|
||||||
|
rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
|
||||||
|
return rememberMe;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http>
|
||||||
|
<remember-me services-ref="rememberMeServices"/>
|
||||||
|
</http>
|
||||||
|
|
||||||
|
<bean id="rememberMeServices" class=
|
||||||
|
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
|
||||||
|
<property name="userDetailsService" ref="myUserDetailsService"/>
|
||||||
|
<property name="key" value="springRocks"/>
|
||||||
|
<property name="matchingAlgorithm" value="MD5"/>
|
||||||
|
<property name="encodingAlgorithm" value="MD5"/>
|
||||||
|
</bean>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
== Propagate ``AuthenticationServiceException``s
|
||||||
|
|
||||||
|
{security-api-url}org/springframework/security/web/authentication/AuthenticationFilter.html[`AuthenticationFilter`] propagates {security-api-url}org/springframework/security/authentication/AuthenticationServiceException.html[``AuthenticationServiceException``]s to the {security-api-url}org/springframework/security/authentication/AuthenticationEntryPoint.html[`AuthenticationEntryPoint`].
|
||||||
|
Because ``AuthenticationServiceException``s represent a server-side error instead of a client-side error, in 6.0, this changes to propagate them to the container.
|
||||||
|
|
||||||
|
=== Configure `AuthenticationFailureHandler` to rethrow ``AuthenticationServiceException``s
|
||||||
|
|
||||||
|
To prepare for the 6.0 default, wire `AuthenticationFilter` instances with a `AuthenticationFailureHandler` that rethrows ``AuthenticationServiceException``s, like so:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
AuthenticationFilter authenticationFilter = new AuthenticationFilter(...);
|
||||||
|
AuthenticationEntryPointFailureHandler handler = new AuthenticationEntryPointFailureHandler(...);
|
||||||
|
handler.setRethrowAuthenticationServiceException(true);
|
||||||
|
authenticationFilter.setAuthenticationFailureHandler(handler);
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
val authenticationFilter: AuthenticationFilter = new AuthenticationFilter(...)
|
||||||
|
val handler: AuthenticationEntryPointFailureHandler = new AuthenticationEntryPointFailureHandler(...)
|
||||||
|
handler.setRethrowAuthenticationServiceException(true)
|
||||||
|
authenticationFilter.setAuthenticationFailureHandler(handler)
|
||||||
|
----
|
||||||
|
|
||||||
|
.Xml
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.AuthenticationFilter">
|
||||||
|
<!-- ... -->
|
||||||
|
<property ref="authenticationFailureHandler"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler">
|
||||||
|
<property name="rethrowAuthenticationServiceException" value="true"/>
|
||||||
|
</bean>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[servlet-authenticationfailurehandler-opt-out]]
|
||||||
|
=== Opt-out Steps
|
||||||
|
|
||||||
|
If rethrowing ``AuthenticationServiceException``s gives you trouble, you can set the value to false instead of taking the 6.0 default, like so:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
AuthenticationFilter authenticationFilter = new AuthenticationFilter(...);
|
||||||
|
AuthenticationEntryPointFailureHandler handler = new AuthenticationEntryPointFailureHandler(...);
|
||||||
|
handler.setRethrowAuthenticationServiceException(false);
|
||||||
|
authenticationFilter.setAuthenticationFailureHandler(handler);
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
val authenticationFilter: AuthenticationFilter = new AuthenticationFilter(...)
|
||||||
|
val handler: AuthenticationEntryPointFailureHandler = new AuthenticationEntryPointFailureHandler(...)
|
||||||
|
handler.setRethrowAuthenticationServiceException(false)
|
||||||
|
authenticationFilter.setAuthenticationFailureHandler(handler)
|
||||||
|
----
|
||||||
|
|
||||||
|
.Xml
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.AuthenticationFilter">
|
||||||
|
<!-- ... -->
|
||||||
|
<property ref="authenticationFailureHandler"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler">
|
||||||
|
<property name="rethrowAuthenticationServiceException" value="false"/>
|
||||||
|
</bean>
|
||||||
|
----
|
||||||
|
====
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,876 @@
|
||||||
|
= Configuration Migrations
|
||||||
|
|
||||||
|
The following steps relate to changes around how to configure `HttpSecurity`, `WebSecurity`, and `AuthenticationManager`.
|
||||||
|
|
||||||
|
[[use-new-requestmatchers]]
|
||||||
|
== Use the new `requestMatchers` methods
|
||||||
|
|
||||||
|
In Spring Security 5.8, the {security-api-url}org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.html#antMatchers(java.lang.String...)[`antMatchers`], {security-api-url}org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.html#mvcMatchers(java.lang.String...)[`mvcMatchers`], and {security-api-url}org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.html#regexMatchers(java.lang.String...)[`regexMatchers`] methods were deprecated in favor of new xref:servlet/authorization/authorize-http-requests.adoc#_request_matchers[`requestMatchers` methods].
|
||||||
|
|
||||||
|
The new `requestMatchers` methods were added xref:servlet/authorization/authorize-http-requests.adoc[to `authorizeHttpRequests`], `authorizeRequests`, CSRF configuration, `WebSecurityCustomizer` and any other places that had the specialized `RequestMatcher` methods.
|
||||||
|
The deprecated methods are removed in Spring Security 6.
|
||||||
|
|
||||||
|
These new methods have more secure defaults since they choose the most appropriate `RequestMatcher` implementation for your application.
|
||||||
|
In summary, the new methods choose the `MvcRequestMatcher` implementation if your application has Spring MVC in the classpath, falling back to the `AntPathRequestMatcher` implementation if Spring MVC is not present (aligning the behavior with the Kotlin equivalent methods).
|
||||||
|
|
||||||
|
To start using the new methods, you can replace the deprecated methods with the new ones. For example, the following application configuration:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.antMatchers("/api/admin/**").hasRole("ADMIN")
|
||||||
|
.antMatchers("/api/user/**").hasRole("USER")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
can be changed to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
||||||
|
.requestMatchers("/api/user/**").hasRole("USER")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If you have Spring MVC in the classpath and are using the `mvcMatchers` methods, you can replace it with the new methods and Spring Security will choose the `MvcRequestMatcher` implementation for you.
|
||||||
|
The following configuration:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableWebMvc
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.mvcMatchers("/admin/**").hasRole("ADMIN")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
is equivalent to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableWebMvc
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If you are customizing the `servletPath` property of the `MvcRequestMatcher`, you can now use the `MvcRequestMatcher.Builder` to create `MvcRequestMatcher` instances that share the same servlet path:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableWebMvc
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.mvcMatchers("/admin").servletPath("/path").hasRole("ADMIN")
|
||||||
|
.mvcMatchers("/user").servletPath("/path").hasRole("USER")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
The code above can be rewritten using the `MvcRequestMatcher.Builder` and the `requestMatchers` method:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableWebMvc
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
|
||||||
|
MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector).servletPath("/path");
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers(mvcMatcherBuilder.pattern("/admin")).hasRole("ADMIN")
|
||||||
|
.requestMatchers(mvcMatcherBuilder.pattern("/user")).hasRole("USER")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If you are having problem with the new `requestMatchers` methods, you can always switch back to the `RequestMatcher` implementation that you were using.
|
||||||
|
For example, if you still want to use `AntPathRequestMatcher` and `RegexRequestMatcher` implementations, you can use the `requestMatchers` method that accepts a `RequestMatcher` instance:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
|
||||||
|
import static org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers(antMatcher("/user/**")).hasRole("USER")
|
||||||
|
.requestMatchers(antMatcher(HttpMethod.POST, "/user/**")).hasRole("ADMIN")
|
||||||
|
.requestMatchers(regexMatcher(".*\\?x=y")).hasRole("SPECIAL") // matches /any/path?x=y
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
Note that the above sample uses static factory methods from {security-api-url}org/springframework/security/web/util/matcher/AntPathRequestMatcher.html[`AntPathRequestMatcher`] and {security-api-url}org/springframework/security/web/util/matcher/RegexRequestMatcher.html[`RegexRequestMatcher`] to improve readability.
|
||||||
|
|
||||||
|
If you are using the `WebSecurityCustomizer` interface, you can replace the deprecated `antMatchers` methods:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||||
|
return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
with their `requestMatchers` counterparts:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||||
|
return (web) -> web.ignoring().requestMatchers("/ignore1", "/ignore2");
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
The same way, if you are customizing the CSRF configuration to ignore some paths, you can replace the deprecated methods with the `requestMatchers` methods:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.csrf((csrf) -> csrf
|
||||||
|
.ignoringAntMatchers("/no-csrf")
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
can be changed to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.csrf((csrf) -> csrf
|
||||||
|
.ignoringRequestMatchers("/no-csrf")
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[use-new-security-matchers]]
|
||||||
|
== Use the new `securityMatchers` methods
|
||||||
|
|
||||||
|
In Spring Security 5.8, the `antMatchers`, `mvcMatchers` and `requestMatchers` methods from `HttpSecurity` were deprecated in favor of new `securityMatchers` methods.
|
||||||
|
|
||||||
|
Note that these methods are not the same from `authorizeHttpRequests` methods <<use-new-requestmatchers,which were deprecated>> in favor of the `requestMatchers` methods.
|
||||||
|
However, the `securityMatchers` methods are similar to the `requestMatchers` methods in the sense that they will choose the most appropriate `RequestMatcher` implementation for your application.
|
||||||
|
In summary, the new methods choose the `MvcRequestMatcher` implementation if your application has Spring MVC in the classpath, falling back to the `AntPathRequestMatcher` implementation if Spring MVC is not present (aligning the behavior with the Kotlin equivalent methods).
|
||||||
|
Another reason for adding the `securityMatchers` methods is to avoid confusion with the `requestMatchers` methods from `authorizeHttpRequests`.
|
||||||
|
|
||||||
|
The following configuration:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.antMatcher("/api/**", "/app/**")
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
can be rewritten using the `securityMatchers` methods:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.securityMatcher("/api/**", "/app/**")
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If you are using a custom `RequestMatcher` in your `HttpSecurity` configuration:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.requestMatcher(new MyCustomRequestMatcher())
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MyCustomRequestMatcher implements RequestMatcher {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
you can do the same using `securityMatcher`:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.securityMatcher(new MyCustomRequestMatcher())
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MyCustomRequestMatcher implements RequestMatcher {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If you are combining multiple `RequestMatcher` implementations in your `HttpSecurity` configuration:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.requestMatchers((matchers) -> matchers
|
||||||
|
.antMatchers("/api/**", "/app/**")
|
||||||
|
.mvcMatchers("/admin/**")
|
||||||
|
.requestMatchers(new MyCustomRequestMatcher())
|
||||||
|
)
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
you can change it by using `securityMatchers`:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.securityMatchers((matchers) -> matchers
|
||||||
|
.requestMatchers("/api/**", "/app/**", "/admin/**")
|
||||||
|
.requestMatchers(new MyCustomRequestMatcher())
|
||||||
|
)
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If you are having problems with the `securityMatchers` methods choosing the `RequestMatcher` implementation for you, you can always choose the `RequestMatcher` implementation yourself:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.securityMatcher(antMatcher("/api/**"), antMatcher("/app/**"))
|
||||||
|
.authorizeHttpRequests((authz) -> authz
|
||||||
|
.requestMatchers(antMatcher("/api/admin/**")).hasRole("ADMIN")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
== Stop Using `WebSecurityConfigurerAdapter`
|
||||||
|
|
||||||
|
=== Publish a `SecurityFilterChain` Bean
|
||||||
|
|
||||||
|
Spring Security 5.4 introduced the capability to publish a `SecurityFilterChain` bean instead of extending `WebSecurityConfigurerAdapter`.
|
||||||
|
In 6.0, `WebSecurityConfigurerAdapter` is removed.
|
||||||
|
To prepare for this change, you can replace constructs like:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.httpBasic(withDefaults());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
open class SecurityConfiguration: WebSecurityConfigurerAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
override fun configure(val http: HttpSecurity) {
|
||||||
|
http {
|
||||||
|
authorizeHttpRequests {
|
||||||
|
authorize(anyRequest, authenticated)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpBasic {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.httpBasic(withDefaults());
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
open class SecurityConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
http {
|
||||||
|
authorizeHttpRequests {
|
||||||
|
authorize(anyRequest, authenticated)
|
||||||
|
}
|
||||||
|
httpBasic {}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
=== Publish a `WebSecurityCustomizer` Bean
|
||||||
|
|
||||||
|
Spring Security 5.4 https://github.com/spring-projects/spring-security/issues/8978[introduced `WebSecurityCustomizer`] to replace `configure(WebSecurity web)` in `WebSecurityConfigurerAdapter`.
|
||||||
|
To prepare for its removal, you can replace code like the following:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(WebSecurity web) {
|
||||||
|
web.ignoring().antMatchers("/ignore1", "/ignore2");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
open class SecurityConfiguration: WebSecurityConfigurerAdapter() {
|
||||||
|
|
||||||
|
override fun configure(val web: WebSecurity) {
|
||||||
|
web.ignoring().antMatchers("/ignore1", "/ignore2")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||||
|
return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
open class SecurityConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun webSecurityCustomizer(): WebSecurityCustomizer {
|
||||||
|
return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
=== Publish an `AuthenticationManager` Bean
|
||||||
|
|
||||||
|
As part of `WebSecurityConfigurerAdapeter` removal, `configure(AuthenticationManagerBuilder)` is also removed.
|
||||||
|
Preparing for its removal will differ based on your reason for using it.
|
||||||
|
|
||||||
|
==== LDAP Authentication
|
||||||
|
|
||||||
|
If you are using `auth.ldapAuthentication()` for xref:servlet/authentication/passwords/ldap.adoc[LDAP authentication support], you can replace:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
auth
|
||||||
|
.ldapAuthentication()
|
||||||
|
.userDetailsContextMapper(new PersonContextMapper())
|
||||||
|
.userDnPatterns("uid={0},ou=people")
|
||||||
|
.contextSource()
|
||||||
|
.port(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
open class SecurityConfiguration: WebSecurityConfigurerAdapter() {
|
||||||
|
|
||||||
|
override fun configure(auth: AuthenticationManagerBuilder) {
|
||||||
|
auth
|
||||||
|
.ldapAuthentication()
|
||||||
|
.userDetailsContextMapper(PersonContextMapper())
|
||||||
|
.userDnPatterns("uid={0},ou=people")
|
||||||
|
.contextSource()
|
||||||
|
.port(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
@Bean
|
||||||
|
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
|
||||||
|
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean =
|
||||||
|
EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
|
||||||
|
contextSourceFactoryBean.setPort(0);
|
||||||
|
return contextSourceFactoryBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) {
|
||||||
|
LdapBindAuthenticationManagerFactory factory =
|
||||||
|
new LdapBindAuthenticationManagerFactory(contextSource);
|
||||||
|
factory.setUserDnPatterns("uid={0},ou=people");
|
||||||
|
factory.setUserDetailsContextMapper(new PersonContextMapper());
|
||||||
|
return factory.createAuthenticationManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
open class SecurityConfiguration {
|
||||||
|
@Bean
|
||||||
|
fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
|
||||||
|
val contextSourceFactoryBean: EmbeddedLdapServerContextSourceFactoryBean =
|
||||||
|
EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer()
|
||||||
|
contextSourceFactoryBean.setPort(0)
|
||||||
|
return contextSourceFactoryBean
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun ldapAuthenticationManager(val contextSource: BaseLdapPathContextSource): AuthenticationManager {
|
||||||
|
val factory = LdapBindAuthenticationManagerFactory(contextSource)
|
||||||
|
factory.setUserDnPatterns("uid={0},ou=people")
|
||||||
|
factory.setUserDetailsContextMapper(PersonContextMapper())
|
||||||
|
return factory.createAuthenticationManager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
==== JDBC Authentication
|
||||||
|
|
||||||
|
If you are using `auth.jdbcAuthentication()` for xref:servlet/authentication/passwords/jdbc.adoc[JDBC Authentication support], you can replace:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
@Bean
|
||||||
|
public DataSource dataSource() {
|
||||||
|
return new EmbeddedDatabaseBuilder()
|
||||||
|
.setType(EmbeddedDatabaseType.H2)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
UserDetails user = User.withDefaultPasswordEncoder()
|
||||||
|
.username("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.build();
|
||||||
|
auth.jdbcAuthentication()
|
||||||
|
.withDefaultSchema()
|
||||||
|
.dataSource(this.dataSource)
|
||||||
|
.withUser(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
open class SecurityConfiguration: WebSecurityConfigurerAdapter() {
|
||||||
|
@Bean
|
||||||
|
fun dataSource(): DataSource {
|
||||||
|
return EmbeddedDatabaseBuilder()
|
||||||
|
.setType(EmbeddedDatabaseType.H2)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configure(val auth: AuthenticationManagerBuilder) {
|
||||||
|
UserDetails user = User.withDefaultPasswordEncoder()
|
||||||
|
.username("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.build()
|
||||||
|
auth.jdbcAuthentication()
|
||||||
|
.withDefaultSchema()
|
||||||
|
.dataSource(this.dataSource)
|
||||||
|
.withUser(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
@Bean
|
||||||
|
public DataSource dataSource() {
|
||||||
|
return new EmbeddedDatabaseBuilder()
|
||||||
|
.setType(EmbeddedDatabaseType.H2)
|
||||||
|
.addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UserDetailsManager users(DataSource dataSource) {
|
||||||
|
UserDetails user = User.withDefaultPasswordEncoder()
|
||||||
|
.username("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.build();
|
||||||
|
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
|
||||||
|
users.createUser(user);
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
open class SecurityConfiguration {
|
||||||
|
@Bean
|
||||||
|
fun dataSource(): DataSource {
|
||||||
|
return EmbeddedDatabaseBuilder()
|
||||||
|
.setType(EmbeddedDatabaseType.H2)
|
||||||
|
.addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun users(val dataSource: DataSource): UserDetailsManager {
|
||||||
|
val user = User.withDefaultPasswordEncoder()
|
||||||
|
.username("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.build()
|
||||||
|
val users = JdbcUserDetailsManager(dataSource)
|
||||||
|
users.createUser(user)
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
==== In-Memory Authentication
|
||||||
|
|
||||||
|
If you are using `auth.inMemoryAuthentication()` for xref:servlet/authentication/passwords/in-memory.adoc[In-Memory Authentication support], you can replace:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
@Override
|
||||||
|
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
UserDetails user = User.withDefaultPasswordEncoder()
|
||||||
|
.username("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.build();
|
||||||
|
auth.inMemoryAuthentication()
|
||||||
|
.withUser(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
open class SecurityConfiguration: WebSecurityConfigurerAdapter() {
|
||||||
|
override fun configure(val auth: AuthenticationManagerBuilder) {
|
||||||
|
val user = User.withDefaultPasswordEncoder()
|
||||||
|
.username("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.build()
|
||||||
|
auth.inMemoryAuthentication()
|
||||||
|
.withUser(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
@Bean
|
||||||
|
public InMemoryUserDetailsManager userDetailsService() {
|
||||||
|
UserDetails user = User.withDefaultPasswordEncoder()
|
||||||
|
.username("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.build();
|
||||||
|
return new InMemoryUserDetailsManager(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
open class SecurityConfiguration {
|
||||||
|
@Bean
|
||||||
|
fun userDetailsService(): InMemoryUserDetailsManager {
|
||||||
|
UserDetails user = User.withDefaultPasswordEncoder()
|
||||||
|
.username("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.build()
|
||||||
|
return InMemoryUserDetailsManager(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
==== Other Scenarios
|
||||||
|
|
||||||
|
If you are using `AuthenticationManagerBuilder` for something more sophisticated, you can xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[publish your own `AuthenticationManager` `@Bean`] or wire an `AuthenticationManager` instance into the `HttpSecurity` DSL with {security-api-url}org/springframework/security/config/annotation/web/builders/HttpSecurity.html#authenticationManager(org.springframework.security.authentication.AuthenticationManager)[`HttpSecurity#authenticationManager`].
|
|
@ -0,0 +1,168 @@
|
||||||
|
= Exploit Protection Migrations
|
||||||
|
|
||||||
|
The following steps relate to changes around how to configure CSRF.
|
||||||
|
|
||||||
|
== Defer Loading CsrfToken
|
||||||
|
|
||||||
|
In Spring Security 5, the default behavior is that the `CsrfToken` will be loaded on every request.
|
||||||
|
This means that in a typical setup, the `HttpSession` must be read for every request even if it is unnecessary.
|
||||||
|
|
||||||
|
In Spring Security 6, the default is that the lookup of the `CsrfToken` will be deferred until it is needed.
|
||||||
|
|
||||||
|
To opt into the new Spring Security 6 default, the following configuration can be used.
|
||||||
|
|
||||||
|
.Defer Loading `CsrfToken`
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||||
|
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
|
||||||
|
// set the name of the attribute the CsrfToken will be populated on
|
||||||
|
requestHandler.setCsrfRequestAttributeName("_csrf");
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.csrf((csrf) -> csrf
|
||||||
|
.csrfTokenRequestHandler(requestHandler)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
val requestHandler = CsrfTokenRequestAttributeHandler()
|
||||||
|
// set the name of the attribute the CsrfToken will be populated on
|
||||||
|
requestHandler.setCsrfRequestAttributeName("_csrf")
|
||||||
|
http {
|
||||||
|
csrf {
|
||||||
|
csrfTokenRequestHandler = requestHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http>
|
||||||
|
<!-- ... -->
|
||||||
|
<csrf request-handler-ref="requestHandler"/>
|
||||||
|
</http>
|
||||||
|
<b:bean id="requestHandler"
|
||||||
|
class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"
|
||||||
|
p:csrfRequestAttributeName="_csrf"/>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If this breaks your application, then you can explicitly opt into the 5.8 defaults using the following configuration:
|
||||||
|
|
||||||
|
.Explicit Configure `CsrfToken` with 5.8 Defaults
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||||
|
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
|
||||||
|
// set the name of the attribute the CsrfToken will be populated on
|
||||||
|
requestHandler.setCsrfRequestAttributeName(null);
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.csrf((csrf) -> csrf
|
||||||
|
.csrfTokenRequestHandler(requestHandler)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
val requestHandler = CsrfTokenRequestAttributeHandler()
|
||||||
|
// set the name of the attribute the CsrfToken will be populated on
|
||||||
|
requestHandler.setCsrfRequestAttributeName(null)
|
||||||
|
http {
|
||||||
|
csrf {
|
||||||
|
csrfTokenRequestHandler = requestHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http>
|
||||||
|
<!-- ... -->
|
||||||
|
<csrf request-handler-ref="requestHandler"/>
|
||||||
|
</http>
|
||||||
|
<b:bean id="requestHandler"
|
||||||
|
class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler">
|
||||||
|
<b:property name="csrfRequestAttributeName">
|
||||||
|
<b:null/>
|
||||||
|
</b:property>
|
||||||
|
</b:bean>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
== CSRF BREACH Protection
|
||||||
|
|
||||||
|
If the steps for <<Defer Loading CsrfToken>> work for you, then you can also opt into Spring Security 6's default support for BREACH protection of the `CsrfToken` using the following configuration:
|
||||||
|
|
||||||
|
.`CsrfToken` BREACH Protection
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||||
|
XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
|
||||||
|
// set the name of the attribute the CsrfToken will be populated on
|
||||||
|
requestHandler.setCsrfRequestAttributeName("_csrf");
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.csrf((csrf) -> csrf
|
||||||
|
.csrfTokenRequestHandler(requestHandler)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
val requestHandler = XorCsrfTokenRequestAttributeHandler()
|
||||||
|
// set the name of the attribute the CsrfToken will be populated on
|
||||||
|
requestHandler.setCsrfRequestAttributeName("_csrf")
|
||||||
|
http {
|
||||||
|
csrf {
|
||||||
|
csrfTokenRequestHandler = requestHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http>
|
||||||
|
<!-- ... -->
|
||||||
|
<csrf request-handler-ref="requestHandler"/>
|
||||||
|
</http>
|
||||||
|
<b:bean id="requestHandler"
|
||||||
|
class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"
|
||||||
|
p:csrfRequestAttributeName="_csrf"/>
|
||||||
|
----
|
||||||
|
====
|
|
@ -0,0 +1,4 @@
|
||||||
|
= Servlet Migrations
|
||||||
|
:page-section-summary-toc: 1
|
||||||
|
|
||||||
|
If you have already performed the xref:migration/index.adoc[initial migration steps] for your Servlet application, you're now ready to perform steps specific to Servlet applications.
|
|
@ -0,0 +1,324 @@
|
||||||
|
= OAuth Migrations
|
||||||
|
|
||||||
|
The following steps relate to changes around how to configure OAuth 2.0.
|
||||||
|
|
||||||
|
== Default authorities for oauth2Login()
|
||||||
|
|
||||||
|
In Spring Security 5, the default `GrantedAuthority` given to a user that authenticates with an OAuth2 or OpenID Connect 1.0 provider (via `oauth2Login()`) is `ROLE_USER`.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
See xref:servlet/oauth2/login/advanced.adoc#oauth2login-advanced-map-authorities[Mapping User Authorities] for more information.
|
||||||
|
====
|
||||||
|
|
||||||
|
In Spring Security 6, the default authority given to a user authenticating with an OAuth2 provider is `OAUTH2_USER`.
|
||||||
|
The default authority given to a user authenticating with an OpenID Connect 1.0 provider is `OIDC_USER`.
|
||||||
|
These defaults allow clearer distinction of users that have authenticated with an OAuth2 or OpenID Connect 1.0 provider.
|
||||||
|
|
||||||
|
If you are using authorization rules or expressions such as `hasRole("USER")` or `hasAuthority("ROLE_USER")` to authorize users with this specific authority, the new defaults in Spring Security 6 will impact your application.
|
||||||
|
|
||||||
|
To opt into the new Spring Security 6 defaults, the following configuration can be used.
|
||||||
|
|
||||||
|
.Configure oauth2Login() with 6.0 defaults
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.oauth2Login((oauth2Login) -> oauth2Login
|
||||||
|
.userInfoEndpoint((userInfo) -> userInfo
|
||||||
|
.userAuthoritiesMapper(grantedAuthoritiesMapper())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private GrantedAuthoritiesMapper grantedAuthoritiesMapper() {
|
||||||
|
return (authorities) -> {
|
||||||
|
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||||
|
|
||||||
|
authorities.forEach((authority) -> {
|
||||||
|
GrantedAuthority mappedAuthority;
|
||||||
|
|
||||||
|
if (authority instanceof OidcUserAuthority) {
|
||||||
|
OidcUserAuthority userAuthority = (OidcUserAuthority) authority;
|
||||||
|
mappedAuthority = new OidcUserAuthority(
|
||||||
|
"OIDC_USER", userAuthority.getIdToken(), userAuthority.getUserInfo());
|
||||||
|
} else if (authority instanceof OAuth2UserAuthority) {
|
||||||
|
OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) authority;
|
||||||
|
mappedAuthority = new OAuth2UserAuthority(
|
||||||
|
"OAUTH2_USER", userAuthority.getAttributes());
|
||||||
|
} else {
|
||||||
|
mappedAuthority = authority;
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedAuthorities.add(mappedAuthority);
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappedAuthorities;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
http {
|
||||||
|
// ...
|
||||||
|
oauth2Login {
|
||||||
|
userInfoEndpoint {
|
||||||
|
userAuthoritiesMapper = grantedAuthoritiesMapper()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun grantedAuthoritiesMapper(): GrantedAuthoritiesMapper {
|
||||||
|
return GrantedAuthoritiesMapper { authorities ->
|
||||||
|
authorities.map { authority ->
|
||||||
|
when (authority) {
|
||||||
|
is OidcUserAuthority ->
|
||||||
|
OidcUserAuthority("OIDC_USER", authority.idToken, authority.userInfo)
|
||||||
|
is OAuth2UserAuthority ->
|
||||||
|
OAuth2UserAuthority("OAUTH2_USER", authority.attributes)
|
||||||
|
else -> authority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http>
|
||||||
|
<oauth2-login user-authorities-mapper-ref="userAuthoritiesMapper" ... />
|
||||||
|
</http>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[servlet-oauth2-login-authorities-opt-out]]
|
||||||
|
=== Opt-out Steps
|
||||||
|
|
||||||
|
If configuring the new authorities gives you trouble, you can opt out and explicitly use the 5.8 authority of `ROLE_USER` with the following configuration.
|
||||||
|
|
||||||
|
.Configure oauth2Login() with 5.8 defaults
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.oauth2Login((oauth2Login) -> oauth2Login
|
||||||
|
.userInfoEndpoint((userInfo) -> userInfo
|
||||||
|
.userAuthoritiesMapper(grantedAuthoritiesMapper())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private GrantedAuthoritiesMapper grantedAuthoritiesMapper() {
|
||||||
|
return (authorities) -> {
|
||||||
|
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||||
|
|
||||||
|
authorities.forEach((authority) -> {
|
||||||
|
GrantedAuthority mappedAuthority;
|
||||||
|
|
||||||
|
if (authority instanceof OidcUserAuthority) {
|
||||||
|
OidcUserAuthority userAuthority = (OidcUserAuthority) authority;
|
||||||
|
mappedAuthority = new OidcUserAuthority(
|
||||||
|
"ROLE_USER", userAuthority.getIdToken(), userAuthority.getUserInfo());
|
||||||
|
} else if (authority instanceof OAuth2UserAuthority) {
|
||||||
|
OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) authority;
|
||||||
|
mappedAuthority = new OAuth2UserAuthority(
|
||||||
|
"ROLE_USER", userAuthority.getAttributes());
|
||||||
|
} else {
|
||||||
|
mappedAuthority = authority;
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedAuthorities.add(mappedAuthority);
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappedAuthorities;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
http {
|
||||||
|
// ...
|
||||||
|
oauth2Login {
|
||||||
|
userInfoEndpoint {
|
||||||
|
userAuthoritiesMapper = grantedAuthoritiesMapper()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun grantedAuthoritiesMapper(): GrantedAuthoritiesMapper {
|
||||||
|
return GrantedAuthoritiesMapper { authorities ->
|
||||||
|
authorities.map { authority ->
|
||||||
|
when (authority) {
|
||||||
|
is OidcUserAuthority ->
|
||||||
|
OidcUserAuthority("ROLE_USER", authority.idToken, authority.userInfo)
|
||||||
|
is OAuth2UserAuthority ->
|
||||||
|
OAuth2UserAuthority("ROLE_USER", authority.attributes)
|
||||||
|
else -> authority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http>
|
||||||
|
<oauth2-login user-authorities-mapper-ref="userAuthoritiesMapper" ... />
|
||||||
|
</http>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
== Deprecations in OAuth2 Client
|
||||||
|
|
||||||
|
In Spring Security 6, deprecated classes and methods were removed from xref:servlet/oauth2/client/index.adoc[OAuth2 Client].
|
||||||
|
Each deprecation is listed below, along with a direct replacement.
|
||||||
|
|
||||||
|
=== `ServletOAuth2AuthorizedClientExchangeFilterFunction`
|
||||||
|
|
||||||
|
The method `setAccessTokenExpiresSkew(...)` can be replaced with one of:
|
||||||
|
|
||||||
|
* `ClientCredentialsOAuth2AuthorizedClientProvider#setClockSkew(...)`
|
||||||
|
* `RefreshTokenOAuth2AuthorizedClientProvider#setClockSkew(...)`
|
||||||
|
* `JwtBearerOAuth2AuthorizedClientProvider#setClockSkew(...)`
|
||||||
|
|
||||||
|
The method `setClientCredentialsTokenResponseClient(...)` can be replaced with the constructor `ServletOAuth2AuthorizedClientExchangeFilterFunction(OAuth2AuthorizedClientManager)`.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
See xref:servlet/oauth2/client/authorization-grants.adoc#oauth2Client-client-creds-grant[Client Credentials] for more information.
|
||||||
|
====
|
||||||
|
|
||||||
|
=== `OidcUserInfo`
|
||||||
|
|
||||||
|
The method `phoneNumberVerified(String)` can be replaced with `phoneNumberVerified(Boolean)`.
|
||||||
|
|
||||||
|
=== `OAuth2AuthorizedClientArgumentResolver`
|
||||||
|
|
||||||
|
The method `setClientCredentialsTokenResponseClient(...)` can be replaced with the constructor `OAuth2AuthorizedClientArgumentResolver(OAuth2AuthorizedClientManager)`.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
See xref:servlet/oauth2/client/authorization-grants.adoc#oauth2Client-client-creds-grant[Client Credentials] for more information.
|
||||||
|
====
|
||||||
|
|
||||||
|
=== `ClaimAccessor`
|
||||||
|
|
||||||
|
The method `containsClaim(...)` can be replaced with `hasClaim(...)`.
|
||||||
|
|
||||||
|
=== `OidcClientInitiatedLogoutSuccessHandler`
|
||||||
|
|
||||||
|
The method `setPostLogoutRedirectUri(URI)` can be replaced with `setPostLogoutRedirectUri(String)`.
|
||||||
|
|
||||||
|
=== `HttpSessionOAuth2AuthorizationRequestRepository`
|
||||||
|
|
||||||
|
The method `setAllowMultipleAuthorizationRequests(...)` has no direct replacement.
|
||||||
|
|
||||||
|
=== `AuthorizationRequestRepository`
|
||||||
|
|
||||||
|
The method `removeAuthorizationRequest(HttpServletRequest)` can be replaced with `removeAuthorizationRequest(HttpServletRequest, HttpServletResponse)`.
|
||||||
|
|
||||||
|
=== `ClientRegistration`
|
||||||
|
|
||||||
|
The method `getRedirectUriTemplate()` can be replaced with `getRedirectUri()`.
|
||||||
|
|
||||||
|
=== `ClientRegistration.Builder`
|
||||||
|
|
||||||
|
The method `redirectUriTemplate(...)` can be replaced with `redirectUri(...)`.
|
||||||
|
|
||||||
|
=== `AbstractOAuth2AuthorizationGrantRequest`
|
||||||
|
|
||||||
|
The constructor `AbstractOAuth2AuthorizationGrantRequest(AuthorizationGrantType)` can be replaced with `AbstractOAuth2AuthorizationGrantRequest(AuthorizationGrantType, ClientRegistration)`.
|
||||||
|
|
||||||
|
=== `ClientAuthenticationMethod`
|
||||||
|
|
||||||
|
The static field `BASIC` can be replaced with `CLIENT_SECRET_BASIC`.
|
||||||
|
|
||||||
|
The static field `POST` can be replaced with `CLIENT_SECRET_POST`.
|
||||||
|
|
||||||
|
=== `OAuth2AccessTokenResponseHttpMessageConverter`
|
||||||
|
|
||||||
|
The field `tokenResponseConverter` has no direct replacement.
|
||||||
|
|
||||||
|
The method `setTokenResponseConverter(...)` can be replaced with `setAccessTokenResponseConverter(...)`.
|
||||||
|
|
||||||
|
The field `tokenResponseParametersConverter` has no direct replacement.
|
||||||
|
|
||||||
|
The method `setTokenResponseParametersConverter(...)` can be replaced with `setAccessTokenResponseParametersConverter(...)`.
|
||||||
|
|
||||||
|
=== `NimbusAuthorizationCodeTokenResponseClient`
|
||||||
|
|
||||||
|
The class `NimbusAuthorizationCodeTokenResponseClient` can be replaced with `DefaultAuthorizationCodeTokenResponseClient`.
|
||||||
|
|
||||||
|
=== `NimbusJwtDecoderJwkSupport`
|
||||||
|
|
||||||
|
The class `NimbusJwtDecoderJwkSupport` can be replaced with `NimbusJwtDecoder` or `JwtDecoders`.
|
||||||
|
|
||||||
|
=== `ImplicitGrantConfigurer`
|
||||||
|
|
||||||
|
The class `ImplicitGrantConfigurer` has no direct replacement.
|
||||||
|
|
||||||
|
[WARNING]
|
||||||
|
====
|
||||||
|
Use of the `implicit` grant type is not recommended and all related support is removed in Spring Security 6.
|
||||||
|
====
|
||||||
|
|
||||||
|
=== `AuthorizationGrantType`
|
||||||
|
|
||||||
|
The static field `IMPLICIT` has no direct replacement.
|
||||||
|
|
||||||
|
[WARNING]
|
||||||
|
====
|
||||||
|
Use of the `implicit` grant type is not recommended and all related support is removed in Spring Security 6.
|
||||||
|
====
|
||||||
|
|
||||||
|
=== `OAuth2AuthorizationResponseType`
|
||||||
|
|
||||||
|
The static field `TOKEN` has no direct replacement.
|
||||||
|
|
||||||
|
[WARNING]
|
||||||
|
====
|
||||||
|
Use of the `implicit` grant type is not recommended and all related support is removed in Spring Security 6.
|
||||||
|
====
|
||||||
|
|
||||||
|
=== `OAuth2AuthorizationRequest`
|
||||||
|
|
||||||
|
The static method `implicit()` has no direct replacement.
|
||||||
|
|
||||||
|
[WARNING]
|
||||||
|
====
|
||||||
|
Use of the `implicit` grant type is not recommended and all related support is removed in Spring Security 6.
|
||||||
|
====
|
||||||
|
|
||||||
|
== Deprecations in OAuth2 Resource Server
|
||||||
|
|
||||||
|
In Spring Security 6, deprecated classes and methods were removed from xref:servlet/oauth2/resource-server/index.adoc[OAuth2 Resource Server].
|
||||||
|
Each deprecation is listed below, along with a direct replacement.
|
||||||
|
|
||||||
|
=== `JwtAuthenticationConverter`
|
||||||
|
|
||||||
|
The method `extractAuthorities(...)` can be replaced with `JwtGrantedAuthoritiesConverter#convert(...)`.
|
||||||
|
|
|
@ -0,0 +1,327 @@
|
||||||
|
= SAML Migrations
|
||||||
|
|
||||||
|
The following steps relate to changes around how to configure SAML 2.0.
|
||||||
|
|
||||||
|
== Use OpenSAML 4
|
||||||
|
|
||||||
|
OpenSAML 3 has reached its end-of-life.
|
||||||
|
As such, Spring Security 6 drops support for it, bumping up its OpenSAML baseline to 4.
|
||||||
|
|
||||||
|
To prepare for the upgrade, update your pom to depend on OpenSAML 4 instead of 3:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Maven
|
||||||
|
[source,maven,role="primary"]
|
||||||
|
----
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-core</artifactId>
|
||||||
|
<version>4.2.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-saml-api</artifactId>
|
||||||
|
<version>4.2.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-saml-impl</artifactId>
|
||||||
|
<version>4.2.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencyManagement>
|
||||||
|
----
|
||||||
|
|
||||||
|
.Gradle
|
||||||
|
[source,gradle,role="secondary"]
|
||||||
|
----
|
||||||
|
dependencies {
|
||||||
|
constraints {
|
||||||
|
api "org.opensaml:opensaml-core:4.2.1"
|
||||||
|
api "org.opensaml:opensaml-saml-api:4.2.1"
|
||||||
|
api "org.opensaml:opensaml-saml-impl:4.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
You must use at least OpenSAML 4.1.1 to update to Spring Security 6's SAML support.
|
||||||
|
|
||||||
|
== Use `OpenSaml4AuthenticationProvider`
|
||||||
|
|
||||||
|
In order to support both OpenSAML 3 and 4 at the same time, Spring Security released `OpenSamlAuthenticationProvider` and `OpenSaml4AuthenticationProvider`.
|
||||||
|
In 6.0, because OpenSAML3 support is removed, `OpenSamlAuthenticationProvider` is removed as well.
|
||||||
|
|
||||||
|
Not all methods in `OpenSamlAuthenticationProvider` were ported 1-to-1 to `OpenSaml4AuthenticationProvider`.
|
||||||
|
As such, some adjustment will be required to make the challenge.
|
||||||
|
|
||||||
|
Consider the following representative usage of `OpenSamlAuthenticationProvider`:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
OpenSamlAuthenticationProvider versionThree = new OpenSamlAuthenticationProvider();
|
||||||
|
versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor);
|
||||||
|
versionThree.setResponseTimeValidationSkew(myDuration);
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
val versionThree: OpenSamlAuthenticationProvider = OpenSamlAuthenticationProvider()
|
||||||
|
versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor)
|
||||||
|
versionThree.setResponseTimeValidationSkew(myDuration)
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
This should change to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
Converter<ResponseToken, Saml2Authentication> delegate = OpenSaml4AuthenticationProvider
|
||||||
|
.createDefaultResponseAuthenticationConverter();
|
||||||
|
OpenSaml4AuthenticationProvider versionFour = new OpenSaml4AuthenticationProvider();
|
||||||
|
versionFour.setResponseAuthenticationConverter((responseToken) -> {
|
||||||
|
Saml2Authentication authentication = delegate.convert(responseToken);
|
||||||
|
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
|
||||||
|
AuthenticatedPrincipal principal = (AuthenticatedPrincipal) authentication.getPrincipal();
|
||||||
|
Collection<GrantedAuthority> authorities = myAuthoritiesExtractor.convert(assertion);
|
||||||
|
return new Saml2Authentication(principal, authentication.getSaml2Response(), authorities);
|
||||||
|
});
|
||||||
|
Converter<AssertionToken, Saml2ResponseValidationResult> validator = OpenSaml4AuthenticationProvider
|
||||||
|
.createDefaultAssertionValidatorWithParameters((p) -> p.put(CLOCK_SKEW, myDuration));
|
||||||
|
versionFour.setAssertionValidator(validator);
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
val delegate = OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter()
|
||||||
|
val versionFour = OpenSaml4AuthenticationProvider()
|
||||||
|
versionFour.setResponseAuthenticationConverter({
|
||||||
|
responseToken -> {
|
||||||
|
val authentication = delegate.convert(responseToken)
|
||||||
|
val assertion = responseToken.getResponse().getAssertions().get(0)
|
||||||
|
val principal = (AuthenticatedPrincipal) authentication.getPrincipal()
|
||||||
|
val authorities = myAuthoritiesExtractor.convert(assertion)
|
||||||
|
return Saml2Authentication(principal, authentication.getSaml2Response(), authorities)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
val validator = OpenSaml4AuthenticationProvider
|
||||||
|
.createDefaultAssertionValidatorWithParameters({ p -> p.put(CLOCK_SKEW, myDuration) })
|
||||||
|
versionFour.setAssertionValidator(validator)
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
== Stop Using SAML 2.0 `Converter` constructors
|
||||||
|
|
||||||
|
In an early release of Spring Security's SAML 2.0 support, `Saml2MetadataFilter` and `Saml2AuthenticationTokenConverter` shipped with constructors of type `Converter`.
|
||||||
|
This level of abstraction made it tricky to evolve the class and so a dedicated interface `RelyingPartyRegistrationResolver` was introduced in a later release.
|
||||||
|
|
||||||
|
In 6.0, the `Converter` constructors are removed.
|
||||||
|
To prepare for this in 5.8, change classes that implement `Converter<HttpServletRequest, RelyingPartyRegistration>` to instead implement `RelyingPartyRegistrationResolver`.
|
||||||
|
|
||||||
|
== Change to Using `Saml2AuthenticationRequestResolver`
|
||||||
|
|
||||||
|
`Saml2AuthenticationContextResolver` and `Saml2AuthenticationRequestFactory` are removed in 6.0 as is the `Saml2WebSsoAuthenticationRequestFilter` that requires them.
|
||||||
|
They are replaced by `Saml2AuthenticationRequestResolver` and a new constructor in `Saml2WebSsoAuthenticationRequestFilter`.
|
||||||
|
The new interface removes an unnecessary transport object between the two classes.
|
||||||
|
|
||||||
|
Most applications need do nothing; however, if you use or configure `Saml2AuthenticationRequestContextResolver` or `Saml2AuthenticationRequestFactory`, try the following steps to convert instead use `Saml2AuthenticationRequestResolver`.
|
||||||
|
|
||||||
|
=== Use `setAuthnRequestCustomizer` instead of `setAuthenticationRequestContextConverter`
|
||||||
|
|
||||||
|
If you are calling `OpenSaml4AuthenticationReqeustFactory#setAuthenticationRequestContextConverter`, for example, like so:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
Saml2AuthenticationRequestFactory authenticationRequestFactory() {
|
||||||
|
OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory();
|
||||||
|
factory.setAuthenticationRequestContextConverter((context) -> {
|
||||||
|
AuthnRequestBuilder authnRequestBuilder = ConfigurationService.get(XMLObjectProviderRegistry.class)
|
||||||
|
.getBuilderFactory().getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
|
||||||
|
IssuerBuilder issuerBuilder = ConfigurationService.get(XMLObjectProviderRegistry.class)
|
||||||
|
.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
|
||||||
|
tring issuer = context.getIssuer();
|
||||||
|
String destination = context.getDestination();
|
||||||
|
String assertionConsumerServiceUrl = context.getAssertionConsumerServiceUrl();
|
||||||
|
String protocolBinding = context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn();
|
||||||
|
AuthnRequest auth = authnRequestBuilder.buildObject();
|
||||||
|
auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||||
|
auth.setIssueInstant(Instant.now());
|
||||||
|
auth.setForceAuthn(Boolean.TRUE);
|
||||||
|
auth.setIsPassive(Boolean.FALSE);
|
||||||
|
auth.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
|
||||||
|
Issuer iss = issuerBuilder.buildObject();
|
||||||
|
iss.setValue(issuer);
|
||||||
|
auth.setIssuer(iss);
|
||||||
|
auth.setDestination(destination);
|
||||||
|
auth.setAssertionConsumerServiceURL(assertionConsumerServiceUrl);
|
||||||
|
});
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
to ensure that ForceAuthn is set to `true`, you can instead do:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationResolver registrations) {
|
||||||
|
OpenSaml4AuthenticationRequestResolver reaolver = new OpenSaml4AuthenticationRequestResolver(registrations);
|
||||||
|
resolver.setAuthnRequestCustomizer((context) -> context.getAuthnRequest().setForceAuthn(Boolean.TRUE));
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
Also, since `setAuthnRequestCustomizer` has direct access to the `HttpServletRequest`, there is no need for a `Saml2AuthenticationRequestContextResolver`.
|
||||||
|
Simply use `setAuthnRequestCustomizer` to read directly from `HttpServletRequest` this information you need.
|
||||||
|
|
||||||
|
=== Use `setAuthnRequestCustomizer` instead of `setProtocolBinding`
|
||||||
|
|
||||||
|
Instead of doing:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
Saml2AuthenticationRequestFactory authenticationRequestFactory() {
|
||||||
|
OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory();
|
||||||
|
factory.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
you can do:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
Saml2AuthenticationRequestResolver authenticationRequestResolver() {
|
||||||
|
OpenSaml4AuthenticationRequestResolver reaolver = new OpenSaml4AuthenticationRequestResolver(registrations);
|
||||||
|
resolver.setAuthnRequestCustomizer((context) -> context.getAuthnRequest()
|
||||||
|
.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"));
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
Since Spring Security only supports the `POST` binding for authentication, there is not very much value in overriding the protocol binding at this point in time.
|
||||||
|
====
|
||||||
|
|
||||||
|
== Use the latest `Saml2AuthenticationToken` constructor
|
||||||
|
|
||||||
|
In an early release, `Saml2AuthenticationToken` took several individual settings as constructor parameters.
|
||||||
|
This created a challenge each time a new parameter needed to be added.
|
||||||
|
Since most of these settings were part of `RelyingPartyRegistration`, a new constructor was added where a `RelyingPartyRegistration` could be provided, making the constructor more stable.
|
||||||
|
It also is valuable in that it more closely aligns with the design of `OAuth2LoginAuthenticationToken`.
|
||||||
|
|
||||||
|
Most applications do not construct this class directly since `Saml2WebSsoAuthenticationFilter` does.
|
||||||
|
However, in the event that your application constructs one, please change from:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
new Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),
|
||||||
|
registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),
|
||||||
|
registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
new Saml2AuthenticationToken(saml2Response, registration)
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
Saml2AuthenticationToken(saml2Response, registration)
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
== Use `RelyingPartyRegistration` updated methods
|
||||||
|
|
||||||
|
In an early release of Spring Security's SAML support, there was some ambiguity on the meaning of certain `RelyingPartyRegistration` methods and their function.
|
||||||
|
As more capabilities were added to `RelyingPartyRegistration`, it became necessary to clarify this ambiguity by changing method names to ones that aligned with spec language.
|
||||||
|
|
||||||
|
The deprecated methods in `RelyingPartyRegstration` are removed.
|
||||||
|
To prepare for that, consider the following representative usage of `RelyingPartyRegistration`:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
String idpEntityId = registration.getRemoteIdpEntityId();
|
||||||
|
String assertionConsumerServiceUrl = registration.getAssertionConsumerServiceUrlTemplate();
|
||||||
|
String idpWebSsoUrl = registration.getIdpWebSsoUrl();
|
||||||
|
String localEntityId = registration.getLocalEntityIdTemplate();
|
||||||
|
List<Saml2X509Credential> verifying = registration.getCredentials().stream()
|
||||||
|
.filter(Saml2X509Credential::isSignatureVerficationCredential)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
val idpEntityId: String = registration.getRemoteIdpEntityId()
|
||||||
|
val assertionConsumerServiceUrl: String = registration.getAssertionConsumerServiceUrlTemplate()
|
||||||
|
val idpWebSsoUrl: String = registration.getIdpWebSsoUrl()
|
||||||
|
val localEntityId: String = registration.getLocalEntityIdTemplate()
|
||||||
|
val verifying: List<Saml2X509Credential> = registration.getCredentials()
|
||||||
|
.filter(Saml2X509Credential::isSignatureVerficationCredential)
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
This should change to:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
String assertingPartyEntityId = registration.getAssertingPartyDetails().getEntityId();
|
||||||
|
String assertionConsumerServiceLocation = registration.getAssertionConsumerServiceLocation();
|
||||||
|
String singleSignOnServiceLocation = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
|
||||||
|
String entityId = registration.getEntityId();
|
||||||
|
List<Saml2X509Credential> verifying = registration.getAssertingPartyDetails().getVerificationX509Credentials();
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
val assertingPartyEntityId: String = registration.getAssertingPartyDetails().getEntityId()
|
||||||
|
val assertionConsumerServiceLocation: String = registration.getAssertionConsumerServiceLocation()
|
||||||
|
val singleSignOnServiceLocation: String = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()
|
||||||
|
val entityId: String = registration.getEntityId()
|
||||||
|
val verifying: List<Saml2X509Credential> = registration.getAssertingPartyDetails().getVerificationX509Credentials()
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
For a complete listing of all changed methods, please see {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[``RelyingPartyRegistration``'s JavaDoc].
|
|
@ -0,0 +1,263 @@
|
||||||
|
= Session Management Migrations
|
||||||
|
|
||||||
|
== Explicit Save SecurityContextRepository
|
||||||
|
|
||||||
|
In Spring Security 5, the default behavior is for the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[`SecurityContext`] to automatically be saved to the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] using the xref:servlet/authentication/persistence.adoc#securitycontextpersistencefilter[`SecurityContextPersistenceFilter`].
|
||||||
|
Saving must be done just prior to the `HttpServletResponse` being committed and just before `SecurityContextPersistenceFilter`.
|
||||||
|
Unfortunately, automatic persistence of the `SecurityContext` can surprise users when it is done prior to the request completing (i.e. just prior to committing the `HttpServletResponse`).
|
||||||
|
It also is complex to keep track of the state to determine if a save is necessary causing unnecessary writes to the `SecurityContextRepository` (i.e. `HttpSession`) at times.
|
||||||
|
|
||||||
|
In Spring Security 6, the default behavior is that the xref:servlet/authentication/persistence.adoc#securitycontextholderfilter[`SecurityContextHolderFilter`] will only read the `SecurityContext` from `SecurityContextRepository` and populate it in the `SecurityContextHolder`.
|
||||||
|
Users now must explicitly save the `SecurityContext` with the `SecurityContextRepository` if they want the `SecurityContext` to persist between requests.
|
||||||
|
This removes ambiguity and improves performance by only requiring writing to the `SecurityContextRepository` (i.e. `HttpSession`) when it is necessary.
|
||||||
|
|
||||||
|
To opt into the new Spring Security 6 default, the following configuration can be used.
|
||||||
|
|
||||||
|
include::partial$servlet/architecture/security-context-explicit.adoc[]
|
||||||
|
|
||||||
|
== Multiple SecurityContextRepository
|
||||||
|
|
||||||
|
In Spring Security 5, the default xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] is `HttpSessionSecurityContextRepository`.
|
||||||
|
|
||||||
|
In Spring Security 6, the default `SecurityContextRepository` is `DelegatingSecurityContextRepository`.
|
||||||
|
To opt into the new Spring Security 6 default, the following configuration can be used.
|
||||||
|
|
||||||
|
.Configure SecurityContextRepository with 6.0 defaults
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.securityContext((securityContext) -> securityContext
|
||||||
|
.securityContextRepository(new DelegatingSecurityContextRepository(
|
||||||
|
new RequestAttributeSecurityContextRepository(),
|
||||||
|
new HttpSessionSecurityContextRepository()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
http {
|
||||||
|
// ...
|
||||||
|
securityContext {
|
||||||
|
securityContextRepository = DelegatingSecurityContextRepository(
|
||||||
|
RequestAttributeSecurityContextRepository(),
|
||||||
|
HttpSessionSecurityContextRepository()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http security-context-repository-ref="contextRepository">
|
||||||
|
<!-- ... -->
|
||||||
|
</http>
|
||||||
|
<bean name="contextRepository"
|
||||||
|
class="org.springframework.security.web.context.DelegatingSecurityContextRepository">
|
||||||
|
<constructor-arg>
|
||||||
|
<bean class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
|
||||||
|
</constructor-arg>
|
||||||
|
<constructor-arg>
|
||||||
|
<bean class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />
|
||||||
|
</constructor-arg>
|
||||||
|
</bean>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[IMPORTANT]
|
||||||
|
====
|
||||||
|
If you are already using an implementation other than `HttpSessionSecurityContextRepository`, you should replace it with your chosen implementation in the example above to ensure that it is used along with `RequestAttributeSecurityContextRepository`.
|
||||||
|
====
|
||||||
|
|
||||||
|
== Deprecation in SecurityContextRepository
|
||||||
|
|
||||||
|
In Spring Security 5.7, a new method was added to xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] with the signature:
|
||||||
|
|
||||||
|
Supplier<SecurityContext> loadContext(HttpServletRequest request)
|
||||||
|
|
||||||
|
With the addition of xref:servlet/authentication/persistence.adoc#delegatingsecuritycontextrepository[`DelegatingSecurityContextRepository`] in Spring Security 5.8, that method was deprecated in favor of a new method with the signature:
|
||||||
|
|
||||||
|
DeferredSecurityContext loadDeferredContext(HttpServletRequest request)
|
||||||
|
|
||||||
|
In Spring Security 6, the deprecated method was removed.
|
||||||
|
If you have implemented `SecurityContextRepository` yourself and added an implementation of the `loadContext(request)` method, you can prepare for Spring Security 6 by removing the implementation of that method and implementing the new method instead.
|
||||||
|
|
||||||
|
To get started implementing the new method, use the following example to provide a `DeferredSecurityContext`:
|
||||||
|
|
||||||
|
.Provide `DeferredSecurityContext`
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Override
|
||||||
|
public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
|
||||||
|
return new DeferredSecurityContext() {
|
||||||
|
private SecurityContext securityContext;
|
||||||
|
private boolean isGenerated;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityContext get() {
|
||||||
|
if (this.securityContext == null) {
|
||||||
|
this.securityContext = getContextOrNull(request);
|
||||||
|
if (this.securityContext == null) {
|
||||||
|
SecurityContextHolderStrategy strategy = SecurityContextHolder.getContextHolderStrategy();
|
||||||
|
this.securityContext = strategy.createEmptyContext();
|
||||||
|
this.isGenerated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.securityContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGenerated() {
|
||||||
|
get();
|
||||||
|
return this.isGenerated;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
override fun loadDeferredContext(request: HttpServletRequest): DeferredSecurityContext {
|
||||||
|
return object : DeferredSecurityContext {
|
||||||
|
private var securityContext: SecurityContext? = null
|
||||||
|
private var isGenerated = false
|
||||||
|
|
||||||
|
override fun get(): SecurityContext {
|
||||||
|
if (securityContext == null) {
|
||||||
|
securityContext = getContextOrNull(request)
|
||||||
|
?: SecurityContextHolder.getContextHolderStrategy().createEmptyContext()
|
||||||
|
.also { isGenerated = true }
|
||||||
|
}
|
||||||
|
return securityContext!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isGenerated(): Boolean {
|
||||||
|
get()
|
||||||
|
return isGenerated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[requestcache-query-optimization]]
|
||||||
|
== Optimize Querying of `RequestCache`
|
||||||
|
|
||||||
|
In Spring Security 5, the default behavior is to query the xref:servlet/architecture.adoc#savedrequests[saved request] on every request.
|
||||||
|
This means that in a typical setup, that in order to use the xref:servlet/architecture.adoc#requestcache[`RequestCache`] the `HttpSession` is queried on every request.
|
||||||
|
|
||||||
|
In Spring Security 6, the default is that `RequestCache` will only be queried for a cached request if the HTTP parameter `continue` is defined.
|
||||||
|
This allows Spring Security to avoid unnecessarily reading the `HttpSession` with the `RequestCache`.
|
||||||
|
|
||||||
|
In Spring Security 5 the default is to use `HttpSessionRequestCache` which will be queried for a cached request on every request.
|
||||||
|
If you are not overriding the defaults (i.e. using `NullRequestCache`), then the following configuration can be used to explicitly opt into the Spring Security 6 behavior in Spring Security 5.8:
|
||||||
|
|
||||||
|
include::partial$servlet/architecture/request-cache-continue.adoc[]
|
||||||
|
|
||||||
|
== Explicit SessionAuthenticationStrategy
|
||||||
|
|
||||||
|
In Spring Security 5, the default configuration relies on `SessionManagementFilter` to detect if a user just authenticated and invoke the `SessionAuthenticationStrategy`.
|
||||||
|
The problem with this is that it means that in a typical setup, the `HttpSession` must be read for every request.
|
||||||
|
|
||||||
|
In Spring Security 6, the default is that authentication mechanisms themselves must invoke the `SessionAuthenticationStrategy`.
|
||||||
|
This means that there is no need to detect when `Authentication` is done and thus the `HttpSession` does not need to be read for every request.
|
||||||
|
|
||||||
|
To opt into the new Spring Security 6 default, the following configuration can be used.
|
||||||
|
|
||||||
|
.Require Explicit `SessionAuthenticationStrategy` Invocation
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.sessionManagement((sessions) -> sessions
|
||||||
|
.requireExplicitAuthenticationStrategy(true)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
http {
|
||||||
|
sessionManagement {
|
||||||
|
requireExplicitAuthenticationStrategy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http>
|
||||||
|
<!-- ... -->
|
||||||
|
<session-management authentication-strategy-explicit-invocation="true"/>
|
||||||
|
</http>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If this breaks your application, then you can explicitly opt into the 5.8 defaults using the following configuration:
|
||||||
|
|
||||||
|
.Explicit use Spring Security 5.8 defaults for `SessionAuthenticationStrategy`
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.sessionManagement((sessions) -> sessions
|
||||||
|
.requireExplicitAuthenticationStrategy(false)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
http {
|
||||||
|
sessionManagement {
|
||||||
|
requireExplicitAuthenticationStrategy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.XML
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<http>
|
||||||
|
<!-- ... -->
|
||||||
|
<session-management authentication-strategy-explicit-invocation="false"/>
|
||||||
|
</http>
|
||||||
|
----
|
||||||
|
====
|
Loading…
Reference in New Issue