diff --git a/docs/modules/ROOT/pages/migration-7/acl.adoc b/docs/modules/ROOT/pages/migration-7/acl.adoc
new file mode 100644
index 0000000000..97c79e768f
--- /dev/null
+++ b/docs/modules/ROOT/pages/migration-7/acl.adoc
@@ -0,0 +1,7 @@
+= ACL
+
+== Favor `AclPermissionEvaluator`
+
+`AclEntryVoter`, `AclEntryAfterInvocationProvider`, and `AclPermissionEvaluator` provide the same service, plugged into different Spring Security APIs. Now that `AccessDecisionVoter` and `AfterInvocationProvider` are both deprecated, the corresponding ACL plugins are obsolete.
+
+As such, begin using `AclPermissionEvaluator` before updating to Spring Security 7.
diff --git a/docs/modules/ROOT/pages/migration-7/authentication.adoc b/docs/modules/ROOT/pages/migration-7/authentication.adoc
index 9c5407ae00..7034702b5a 100644
--- a/docs/modules/ROOT/pages/migration-7/authentication.adoc
+++ b/docs/modules/ROOT/pages/migration-7/authentication.adoc
@@ -1,68 +1,2 @@
= Authentication Changes
-== Opaque Token Credentials Will Be Encoded For You
-
-In order to comply more closely with the Introspection RFC, Spring Security's opaque token support will encode the client id and secret before creating the authorization header.
-This change means you will no longer have to encode the client id and secret yourself.
-
-If your client id or secret contain URL-unsafe characters, then you can prepare yourself for this change by doing the following:
-
-=== Replace Usage of `introspectionClientCredentials`
-
-Since Spring Security can now do the encoding for you, replace xref:servlet/oauth2/resource-server/opaque-token.adoc#oauth2resourceserver-opaque-introspectionuri-dsl[using `introspectionClientCredentials`] with publishing the following `@Bean`:
-
-[tabs]
-======
-Java::
-+
-[source,java,role="primary"]
-----
-@Bean
-OpaqueTokenIntrospector introspector() {
- return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri)
- .clientId(unencodedClientId).clientSecret(unencodedClientSecret).build();
-}
-----
-
-Kotlin::
-+
-[source,kotlin,role="secondary"]
-----
-@Bean
-fun introspector(): OpaqueTokenIntrospector {
- return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri)
- .clientId(unencodedClientId).clientSecret(unencodedClientSecret).build()
-}
-----
-======
-
-The above will be the default in 7.0.
-
-If this setting gives you trouble or you cannot apply it for now, you can use the `RestOperations` constructor instead:
-
-[tabs]
-======
-Java::
-+
-[source,java,role="primary"]
-----
-@Bean
-OpaqueTokenIntrospector introspector() {
- RestTemplate rest = new RestTemplate();
- rest.addInterceptor(new BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret));
- return new SpringOpaqueTokenIntrospector(introspectionUri, rest);
-}
-----
-
-Kotlin::
-+
-[source,kotlin,role="secondary"]
-----
-@Bean
-fun introspector(): OpaqueTokenIntrospector {
- val rest = RestTemplate()
- rest.addInterceptor(BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret))
- return SpringOpaqueTokenIntrospector(introspectionUri, rest)
-}
-----
-======
diff --git a/docs/modules/ROOT/pages/migration-7/authorization.adoc b/docs/modules/ROOT/pages/migration-7/authorization.adoc
index 031c8a8bfb..0c0801285c 100644
--- a/docs/modules/ROOT/pages/migration-7/authorization.adoc
+++ b/docs/modules/ROOT/pages/migration-7/authorization.adoc
@@ -22,3 +22,82 @@ public void doSomething(Long id) {
You must compile with `-parameters` to ensure that the parameter names are available at runtime.
For more information about this, please visit the https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#core-container[Upgrading to Spring Framework 6.1 page].
+
+=== Favor `AnnotationTemplateExpressionDefaults` over `PrePostTemplateDefaults`
+
+In Spring Security 7, `AnnotationTemplateExpressionDefaults` will be included by default.
+
+If you are customizing `PrePostTemplateDefaults` or simply want to see how your application responds to `AnnotationTemplateExpressionDefaults`, you can publish an `AnnotationTemplateExpressionDefaults` bean instead of a `PrePostTemplateDefaults` method:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+static AnnotationTemplateExpressionDefaults templateExpressionDefaults() {
+ return new AnnotationTemplateExpressionDefaults();
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+companion object {
+ @Bean
+ fun templateExpressionDefaults() = AnnotationTemplateExpressionDefaults()
+}
+----
+
+Xml::
++
+[source,xml,role="secondary"]
+----
+
+----
+======
+
+==== I Am Publishing an AuthorizationAdvisor Bean
+
+If you are publishing an `AuthorizationAdvisor` bean, like `AuthorizationManagerBeforeMethodInterceptor`, `AuthorizationManagerAfterMethodInterceptor`, `PreFilterAuthorizationMethodInterceptor`, or `PostFilterAuthorizationMethodInterceptor`, you can do the same by calling `setTemplateDefaults` with an `AnnotationTemplateExpressionDefaults` instance instead:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+@Role(BeanDescription.ROLE_INFRASTRUCTURE)
+static Advisor preFilter() {
+ PreFilterAuthorizationMethodInterceptor interceptor = new PreFilterAuthorizationMethodInterceptor();
+ interceptor.setTemplateDefaults(new AnnotationTemplateExpressionDefaults());
+ return interceptor;
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+companion object {
+ @Bean
+ @Role(BeanDescription.ROLE_INFRASTRUCTURE)
+ fun preFilter(): Advisor {
+ val interceptor = PreFilterAuthorizationMethodInterceptor()
+ interceptor.setTemplateDefaults(AnnotationTemplateExpressionDefaults)
+ return interceptor
+ }
+}
+----
+======
+
+=== Publish `AuthorizationAdvisor` instances instead of adding them in a `Customizer`
+
+While the ability to customize the `AuthorizationAdvisorProxyFactory` instance will remain in Spring Security 7, the ability to add advisors will be removed in favor of picking up published `AuthorizationAdvisor` beans.
+
+If you are not calling `AuthorizationAdvisorProxyFactory#setAdvisors` or `AuthorizationAdvisorProxyFactory#addAdvisor`, you need do nothing.
+
+If you are, publish the `AuthorizationAdvisor` bean instead and Spring Security will pick it up and apply it automatically.
diff --git a/docs/modules/ROOT/pages/migration-7/configuration.adoc b/docs/modules/ROOT/pages/migration-7/configuration.adoc
index 0a06694f95..8d84e04ab4 100644
--- a/docs/modules/ROOT/pages/migration-7/configuration.adoc
+++ b/docs/modules/ROOT/pages/migration-7/configuration.adoc
@@ -123,3 +123,60 @@ In versions prior to 6.2, if you had a xref:servlet/configuration/java.adoc#jc-c
However, starting from version 6.2, this method is deprecated and will be removed in 7.0 because it will no longer be possible to chain configurations using `.and()` once `.and()` is removed (see https://github.com/spring-projects/spring-security/issues/13067).
Instead, it is recommended to use the new `.with(...)` method.
For more information about how to use `.with(...)` please refer to the xref:servlet/configuration/java.adoc#jc-custom-dsls[Custom DSLs section].
+
+== Use `dispatcherTypeMatchers` instead of `shouldFilterAllDispatcherTypes`
+
+If you are permitting the ERROR dispatch, you may be using `shouldFilterAllDispatcherTypes(false)` in the `auhorizeHttpRequests` DSL:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+http
+ .authorizeHttpRequests((authorize) -> authorize
+ .shouldFilterAllDispatcherTypes(false)
+ // ...
+ )
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+http {
+ authorizeHttpRequests {
+ shouldFilterAllDispatcherTypes = false
+ // ...
+ }
+}
+----
+======
+
+In preparation for 7, change this to use `dispatcherTypeMatchers`:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+http
+ .authorizHttpRequests((authorize) -> authorize
+ .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
+ // ...
+ )
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+http {
+ authorizeHttpRequests {
+ authorize(new DispatcherTypeRequestMatcher(DispatcherType.ERROR), permitAll())
+ }
+}
+----
+======
diff --git a/docs/modules/ROOT/pages/migration-7/oauth2.adoc b/docs/modules/ROOT/pages/migration-7/oauth2.adoc
index 1c3b9b43e2..95cdc1bf71 100644
--- a/docs/modules/ROOT/pages/migration-7/oauth2.adoc
+++ b/docs/modules/ROOT/pages/migration-7/oauth2.adoc
@@ -170,3 +170,70 @@ fun jwtDecoder(): JwtDecoder {
<2> - specify the list of validators you need, excluding `JwtTypeValidator`
For additional guidance, please see the xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-validation[JwtDecoder Validators] section in the reference.
+
+== Opaque Token Credentials Will Be Encoded For You
+
+In order to comply more closely with the Introspection RFC, Spring Security's opaque token support will encode the client id and secret before creating the authorization header.
+This change means you will no longer have to encode the client id and secret yourself.
+
+If your client id or secret contain URL-unsafe characters, then you can prepare yourself for this change by doing the following:
+
+=== Replace Usage of `introspectionClientCredentials`
+
+Since Spring Security can now do the encoding for you, replace xref:servlet/oauth2/resource-server/opaque-token.adoc#oauth2resourceserver-opaque-introspectionuri-dsl[using `introspectionClientCredentials`] with publishing the following `@Bean`:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+OpaqueTokenIntrospector introspector() {
+ return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri)
+ .clientId(unencodedClientId).clientSecret(unencodedClientSecret).build();
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun introspector(): OpaqueTokenIntrospector {
+ return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri)
+ .clientId(unencodedClientId).clientSecret(unencodedClientSecret).build()
+}
+----
+======
+
+The above will be the default in 7.0.
+
+If this setting gives you trouble or you cannot apply it for now, you can use the `RestOperations` constructor instead:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+OpaqueTokenIntrospector introspector() {
+ RestTemplate rest = new RestTemplate();
+ rest.addInterceptor(new BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret));
+ return new SpringOpaqueTokenIntrospector(introspectionUri, rest);
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun introspector(): OpaqueTokenIntrospector {
+ val rest = RestTemplate()
+ rest.addInterceptor(BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret))
+ return SpringOpaqueTokenIntrospector(introspectionUri, rest)
+}
+----
+======
diff --git a/docs/modules/ROOT/pages/migration-7/saml2.adoc b/docs/modules/ROOT/pages/migration-7/saml2.adoc
index eed236a64b..43da1be2b0 100644
--- a/docs/modules/ROOT/pages/migration-7/saml2.adoc
+++ b/docs/modules/ROOT/pages/migration-7/saml2.adoc
@@ -1,5 +1,17 @@
= Saml 2.0 Migrations
+== Use OpenSAML 5 By Default
+
+OpenSAML 4.x is no longer supported by the OpenSAML team.
+As such, Spring Security will default to using its `OpenSaml5` components in all cases.
+
+If you want to see how well your application will respond to this, do the following:
+
+1. Update your OpenSAML dependencies to 5.x
+2. If you are constructing an `OpenSaml4XXX` Spring Security component, change it to `OpenSaml5`.
+
+If you cannot opt-in, then add the `opensaml-saml-api` and `opensaml-saml-impl` 4.x dependencies and exclude the 5.x dependencies from `spring-security-saml2-service-provider`.
+
== Continue Filter Chain When No Relying Party Found
In Spring Security 6, `Saml2WebSsoAuthenticationFilter` throws an exception when the request URI matches, but no relying party registration is found.
@@ -163,3 +175,230 @@ val responseValidator = ResponseValidator.withDefaults { responseToken: Response
provider.setResponseValidator(responseValidator)
----
======
+
+== `RelyingPartyRegistration` Improvements
+
+`RelyingPartyRegistration` links metadata from a relying party to metadata from an asserting party.
+
+To prepare for some improvements to the API, please take the following steps:
+
+1. If you are mutating a registration by using `RelyingPartyRegistration#withRelyingPartyRegistration`, instead call `RelyingPartyRegistration#mutate`
+2. If you are providing or retrieving `AssertingPartyDetails`, use `getAssertingPartyMetadata` or `withAssertingPartyMetadata` instead.
+
+== `OpenSaml5AuthenticationProvider` Improvements
+
+Spring Security 7 will remove a handful of static factories from `OpenSaml5AuthenticationProvider` in favor of inner classes.
+These inner classes simplify customization of the response validator, the assertion validator, and the response authentication converter.
+
+=== Response Validation
+
+Instead of doing:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
+ OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
+ saml2.setResponseValidator((responseToken) -> OpenSamlAuthenticationProvider.createDefaultResponseValidator()
+ .andThen((result) -> result
+ .concat(myCustomValidator.convert(responseToken))
+ ));
+ return saml2;
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
+ val saml2 = OpenSaml5AuthenticationProvider()
+ saml2.setResponseValidator { responseToken -> OpenSamlAuthenticationProvider.createDefaultResponseValidator()
+ .andThen { result -> result
+ .concat(myCustomValidator.convert(responseToken))
+ }
+ }
+ return saml2
+}
+----
+======
+
+use `OpenSaml5AuthenticationProvider.ResponseValidator`:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
+ OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
+ saml2.setResponseValidator(ResponseValidator.withDefaults(myCustomValidator));
+ return saml2;
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
+ val saml2 = OpenSaml5AuthenticationProvider()
+ saml2.setResponseValidator(ResponseValidator.withDefaults(myCustomValidator))
+ return saml2
+}
+----
+======
+
+=== Assertion Validation
+
+Instead of doing:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
+ OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
+ authenticationProvider.setAssertionValidator(OpenSaml5AuthenticationProvider
+ .createDefaultAssertionValidatorWithParameters(assertionToken -> {
+ Map params = new HashMap<>();
+ params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis());
+ // ... other validation parameters
+ return new ValidationContext(params);
+ })
+ );
+ return saml2;
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
+ val saml2 = OpenSaml5AuthenticationProvider()
+ authenticationProvider.setAssertionValidator(OpenSaml5AuthenticationProvider
+ .createDefaultAssertionValidatorWithParameters { ->
+ val params = HashMap()
+ params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis())
+ // ... other validation parameters
+ return ValidationContext(params)
+ }
+ )
+ return saml2
+}
+----
+======
+
+use `OpenSaml5AuthenticationProvider.AssertionValidator`:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
+ OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
+ Duration tenMinutes = Duration.ofMinutes(10);
+ authenticationProvider.setAssertionValidator(AssertionValidator.builder().clockSkew(tenMinutes).build());
+ return saml2;
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
+ val saml2 = OpenSaml5AuthenticationProvider()
+ val tenMinutes = Duration.ofMinutes(10)
+ authenticationProvider.setAssertionValidator(AssertionValidator.builder().clockSkew(tenMinutes).build())
+ return saml2
+}
+----
+======
+
+== Response Authentication Converter
+
+Instead of doing:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+Converter authenticationConverter() {
+ return (responseToken) -> {
+ Saml2Authentication authentication = OpenSaml5AutnenticationProvider.createDefaultResponseAuthenticationConverter()
+ .convert(responseToken);
+ // ... work with OpenSAML's Assertion object to extract the principal
+ return new Saml2Authentication(myPrincipal, authentication.getSaml2Response(), authentication.getAuthorities());
+ };
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun authenticationConverter(): Converter {
+ return { responseToken ->
+ val authentication =
+ OpenSaml5AutnenticationProvider.createDefaultResponseAuthenticationConverter().convert(responseToken)
+ // ... work with OpenSAML's Assertion object to extract the principal
+ return Saml2Authentication(myPrincipal, authentication.getSaml2Response(), authentication.getAuthorities())
+ }
+}
+----
+======
+
+use `OpenSaml5AuthenticationProvider.ResponseAuthenticationConverter`:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+ResponseAuthenticationConverter authenticationConverter() {
+ ResponseAuthenticationConverter authenticationConverter = new ResponseAuthenticationConverter();
+ authenticationConverter.setPrincipalNameConverter((assertion) -> {
+ // ... work with OpenSAML's Assertion object to extract the principal
+ });
+ return authenticationConverter;
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun authenticationConverter(): ResponseAuthenticationConverter {
+ val authenticationConverter = ResponseAuthenticationConverter()
+ authenticationConverter.setPrincipalNameConverter { assertion ->
+ // ... work with OpenSAML's Assertion object to extract the principal
+ }
+ return authenticationConverter
+}
+----
+======
diff --git a/docs/modules/ROOT/pages/migration-7/web.adoc b/docs/modules/ROOT/pages/migration-7/web.adoc
index 467f2663e8..248b719c0e 100644
--- a/docs/modules/ROOT/pages/migration-7/web.adoc
+++ b/docs/modules/ROOT/pages/migration-7/web.adoc
@@ -521,3 +521,49 @@ Xml::
=====
If you have several circumstances where HTTP is needed, consider using `OrRequestMatcher` to combine them into a single `RequestMatcher` instance.
=====
+
+== Use `setCookieCustomizer` instead of individual setters
+
+In favor of a simpler API, `CookieCsrfTokenRepository#setCookieCustomizer` allows you to change any aspect of the cookie, replacing `setCookieHttpOnly`, `setCookieMaxAge`, `setSecure`, and `setCookieDomain`.
+
+Change this:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+CookeCsrfTokenRepository csrf = CookeCsrfTokenRepository.withHttpOnlyFalse();
+csrf.setCookieMaxAge(86400)
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+val csrf = CookeCsrfTokenRepository.withHttpOnlyFalse()
+csrf.setCookieMaxAge(86400)
+----
+======
+
+to this:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+CookeCsrfTokenRepository csrf = CookeCsrfTokenRepository.withHttpOnlyFalse();
+csrf.setCookieCustomizer((c) -> c.maxAge(86400));
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+val csrf = CookeCsrfTokenRepository.withHttpOnlyFalse()
+csrf.setCookieCustomizer { -> it.maxAge(86400) }
+----
+======