瀏覽代碼

Merge branch '6.5.x'

Josh Cummings 3 月之前
父節點
當前提交
9c357984d7

+ 7 - 0
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.

+ 0 - 66
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)
-}
-----
-======

+ 79 - 0
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"]
+----
+<b:bean id="templateExpressionDefaults" class="org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults"/>
+----
+======
+
+==== 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<AuthorizationAdvisorProxyFactory>`
+
+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.

+ 57 - 0
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())
+    }
+}
+----
+======

+ 67 - 0
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)
+}
+----
+======

+ 239 - 0
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<String, Object> 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<String, Object>()
+            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<ResponseToken, Saml2Authentication> 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<ResponseToken, Saml2Authentication> {
+    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
+}
+----
+======

+ 46 - 0
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) }
+----
+======