parent
3d0be9beba
commit
7675874137
|
@ -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
|
@ -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