Restructure Migration Steps

CLoses gh-12224
This commit is contained in:
Josh Cummings 2022-11-16 11:35:47 -07:00
parent 3d0be9beba
commit 7675874137
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
12 changed files with 4336 additions and 4300 deletions

View File

@ -2,7 +2,16 @@
* xref:prerequisites.adoc[Prerequisites]
* xref:community.adoc[Community]
* 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:features/index.adoc[Features]
** xref:features/authentication/index.adoc[Authentication]

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -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"/>
----
====

View File

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

View File

@ -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(...)`.

View File

@ -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].

View File

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