From 76758741375fa1e06b9a9e7f3c15b20c85221506 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 16 Nov 2022 11:35:47 -0700 Subject: [PATCH] Restructure Migration Steps CLoses gh-12224 --- docs/modules/ROOT/nav.adoc | 11 +- docs/modules/ROOT/pages/migration.adoc | 4299 ----------------- docs/modules/ROOT/pages/migration/index.adoc | 364 ++ .../ROOT/pages/migration/reactive.adoc | 197 + .../migration/servlet/authentication.adoc | 244 + .../migration/servlet/authorization.adoc | 1559 ++++++ .../ROOT/pages/migration/servlet/config.adoc | 876 ++++ .../pages/migration/servlet/exploits.adoc | 168 + .../ROOT/pages/migration/servlet/index.adoc | 4 + .../ROOT/pages/migration/servlet/oauth2.adoc | 324 ++ .../ROOT/pages/migration/servlet/saml2.adoc | 327 ++ .../migration/servlet/session-management.adoc | 263 + 12 files changed, 4336 insertions(+), 4300 deletions(-) delete mode 100644 docs/modules/ROOT/pages/migration.adoc create mode 100644 docs/modules/ROOT/pages/migration/index.adoc create mode 100644 docs/modules/ROOT/pages/migration/reactive.adoc create mode 100644 docs/modules/ROOT/pages/migration/servlet/authentication.adoc create mode 100644 docs/modules/ROOT/pages/migration/servlet/authorization.adoc create mode 100644 docs/modules/ROOT/pages/migration/servlet/config.adoc create mode 100644 docs/modules/ROOT/pages/migration/servlet/exploits.adoc create mode 100644 docs/modules/ROOT/pages/migration/servlet/index.adoc create mode 100644 docs/modules/ROOT/pages/migration/servlet/oauth2.adoc create mode 100644 docs/modules/ROOT/pages/migration/servlet/saml2.adoc create mode 100644 docs/modules/ROOT/pages/migration/servlet/session-management.adoc diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index a8a7057835..a88743a893 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -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] diff --git a/docs/modules/ROOT/pages/migration.adoc b/docs/modules/ROOT/pages/migration.adoc deleted file mode 100644 index a749fbdce6..0000000000 --- a/docs/modules/ROOT/pages/migration.adoc +++ /dev/null @@ -1,4299 +0,0 @@ -[[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. - -== Servlet - -=== 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"] ----- - - - - ----- -==== - -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"] ----- - - - - ----- -==== - -=== 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"] ----- - - - - - ----- -==== - -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"] ----- - - - - - - - - - ----- -==== - -=== CSRF BREACH Protection - -If the steps for <> 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"] ----- - - - - - ----- -==== - -=== 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"] ----- - - - - - - - - - - - ----- -==== - -[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 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[] - -=== Use `AuthorizationManager` for Method Security - -xref:servlet/authorization/method-security.adoc[Method Security] has been xref:servlet/authorization/method-security.adoc#jc-enable-method-security[simplified] 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, note that `@EnableGlobalMethodSecurity`, while deprecated, will not be removed in 6.0, allowing you to opt out by sticking with the old annotation. - -[[servlet-replace-globalmethodsecurity-with-methodsecurity]] -==== Replace xref:servlet/authorization/method-security.adoc#jc-enable-global-method-security[global method security] with xref:servlet/authorization/method-security.adoc#jc-enable-method-security[method security] - -{security-api-url}org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.html[`@EnableGlobalMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-global-method-security[``] are deprecated in favor of {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.html[`@EnableMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-method-security[``], respectively. -The new annotation and XML element activate Spring's xref:servlet/authorization/method-security.adoc#jc-enable-method-security[pre-post annotations] by default and use `AuthorizationManager` internally. - -This means that the following two listings are functionally equivalent: - -==== -.Java -[source,java,role="primary"] ----- -@EnableGlobalMethodSecurity(prePostEnabled = true) ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@EnableGlobalMethodSecurity(prePostEnabled = true) ----- - -.Xml -[source,xml,role="secondary"] ----- - ----- -==== - -and: - -==== -.Java -[source,java,role="primary"] ----- -@EnableMethodSecurity ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@EnableMethodSecurity ----- - -.Xml -[source,xml,role="secondary"] ----- - ----- -==== - -For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior. - -For example, a listing like: - -==== -.Java -[source,java,role="primary"] ----- -@EnableGlobalMethodSecurity(securedEnabled = true) ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@EnableGlobalMethodSecurity(securedEnabled = true) ----- - -.Xml -[source,xml,role="secondary"] ----- - ----- -==== - -should change to: - -==== -.Java -[source,java,role="primary"] ----- -@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false) ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false) ----- - -.Xml -[source,xml,role="secondary"] ----- - ----- -==== - -[[servlet-replace-permissionevaluator-bean-with-methodsecurityexpression-handler]] -==== Publish a `MethodSecurityExpressionHandler` instead of a `PermissionEvaluator` - -`@EnableMethodSecurity` does not pick up a `PermissionEvaluator`. -This helps keep its API simple. - -If you have a custom {security-api-url}org/springframework/security/access/PermissionEvaluator.html[`PermissionEvaluator`] `@Bean`, please change it from: - -==== -.Java -[source,java,role="primary"] ----- -@Bean -static PermissionEvaluator permissionEvaluator() { - // ... your evaluator -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -companion object { - @Bean - fun permissionEvaluator(): PermissionEvaluator { - // ... your evaluator - } -} ----- -==== - -to: - -==== -.Java -[source,java,role="primary"] ----- -@Bean -static MethodSecurityExpressionHandler expressionHandler() { - var expressionHandler = new DefaultMethodSecurityExpressionHandler(); - expressionHandler.setPermissionEvaluator(myPermissionEvaluator); - return expressionHandler; -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -companion object { - @Bean - fun expressionHandler(): MethodSecurityExpressionHandler { - val expressionHandler = DefaultMethodSecurityExpressionHandler - expressionHandler.setPermissionEvaluator(myPermissionEvaluator) - return expressionHandler - } -} ----- -==== - -==== Replace any custom method-security ``AccessDecisionManager``s - -Your application may have a custom {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] or {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] arrangement. -The preparation strategy will depend on your reason for each arrangement. -Read on to find the best match for your situation. - -===== I use `UnanimousBased` - -If your application uses {security-api-url}org/springframework/security/access/vote/UnanimousBased.html[`UnanimousBased`] with the default voters, you likely need do nothing since unanimous-based is the default behavior with {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.html[`@EnableMethodSecurity`]. - -However, if you do discover that you cannot accept the default authorization managers, you can use `AuthorizationManagers.allOf` to compose your own arrangement. -Having done that, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. - -===== I use `AffirmativeBased` - -If your application uses {security-api-url}org/springframework/security/access/vote/AffirmativeBased.html[`AffirmativeBased`], then you can construct an equivalent {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`], like so: - -==== -.Java -[source,java,role="primary"] ----- -AuthorizationManager authorization = AuthorizationManagers.anyOf( - // ... your list of authorization managers -) ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -val authorization = AuthorizationManagers.anyOf( - // ... your list of authorization managers -) ----- -==== - -Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. - -===== I use `ConsensusBased` - -There is no framework-provided equivalent for {security-api-url}org/springframework/security/access/vote/ConsensusBased.html[`ConsensusBased`]. -In that case, please implement a composite {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] that takes the set of delegate ``AuthorizationManager``s into account. - -Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. - -===== I use a custom `AccessDecisionVoter` - -You should either change the class to implement {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] or create an adapter. - -Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution. -By way of example, though, here is what adapting {security-api-url}org/springframework/security/access/SecurityMetadataSource.html[`SecurityMetadataSource`] and {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] for `@PreAuthorize` would look like: - -==== -.Java -[source,java,role="primary"] ----- -public final class PreAuthorizeAuthorizationManagerAdapter implements AuthorizationManager { - private final SecurityMetadataSource metadata; - private final AccessDecisionVoter voter; - - public PreAuthorizeAuthorizationManagerAdapter(MethodSecurityExpressionHandler expressionHandler) { - ExpressionBasedAnnotationAttributeFactory attributeFactory = - new ExpressionBasedAnnotationAttributeFactory(expressionHandler); - this.metadata = new PrePostAnnotationSecurityMetadataSource(attributeFactory); - ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice(); - expressionAdvice.setExpressionHandler(expressionHandler); - this.voter = new PreInvocationAuthorizationAdviceVoter(expressionAdvice); - } - - public AuthorizationDecision check(Supplier authentication, MethodInvocation invocation) { - List attributes = this.metadata.getAttributes(invocation, AopUtils.getTargetClass(invocation.getThis())); - int decision = this.voter.vote(authentication.get(), invocation, attributes); - if (decision == ACCESS_GRANTED) { - return new AuthorizationDecision(true); - } - if (decision == ACCESS_DENIED) { - return new AuthorizationDecision(false); - } - return null; // abstain - } -} ----- -==== - -Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. - -===== I use a custom `AfterInvocationManager` - -{security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] replaces both {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] and {security-api-url}org/springframework/security/access/intercept/AfterInvocationManager.html[`AfterInvocationManager`]. -The difference is that `AuthorizationManager` replaces `AccessDecisionManager` and `AuthorizationManager` replaces `AfterInvocationManager`. - -Given that, <<_i_use_a_custom_accessdecisionvoter,the same rules apply for adaptation>>, where the goal this time is to implement `AuthorizationManager` instead of `AuthorizationManager` and use `AuthorizationManagerAfterMethodInterceptor` instead of `AuthorizationManagerBeforeMethodInterceptor`. - -===== I use `RunAsManager` - -There is currently https://github.com/spring-projects/spring-security/issues/11331[no replacement for `RunAsManager`] though one is being considered. - -It is quite straightforward to adapt a `RunAsManager`, though, to the `AuthorizationManager` API, if needed. - -Here is some pseudocode to get you started: - -==== -.Java -[source,java,role="primary"] ----- -public final class RunAsAuthorizationManagerAdapter implements AuthorizationManager { - private final RunAsManager runAs = new RunAsManagerImpl(); - private final SecurityMetadataSource metadata; - private final AuthorizationManager authorization; - - // ... constructor - - public AuthorizationDecision check(Supplier authentication, T object) { - Supplier wrapped = (auth) -> { - List attributes = this.metadata.getAttributes(object); - return this.runAs.buildRunAs(auth, object, attributes); - }; - return this.authorization.check(wrapped, object); - } -} ----- -==== - -Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. - -[[servlet-check-for-annotationconfigurationexceptions]] -==== Check for ``AnnotationConfigurationException``s - -`@EnableMethodSecurity` and `` activate stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations. -If after moving to either you see ``AnnotationConfigurationException``s in your logs, follow the instructions in the exception message to clean up your application's method security annotation usage. - -=== Use `AuthorizationManager` for Message Security - -xref:servlet/integrations/websocket.adoc[Message Security] has been xref:servlet/integrations/websocket.adoc#websocket-configuration[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 <> at the end of this section. - -==== Ensure all messages have defined authorization rules - -The now-deprecated {security-api-url}org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.html[message security support] permits all messages by default. -xref:servlet/integrations/websocket.adoc[The new support] has the stronger default of denying all messages. - -To prepare for this, ensure that authorization rules exist are declared for every request. - -For example, an application configuration like: - -==== -.Java -[source,java,role="primary"] ----- -@Override -protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { - messages - .simpDestMatchers("/user/queue/errors").permitAll() - .simpDestMatchers("/admin/**").hasRole("ADMIN"); -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { - messages - .simpDestMatchers("/user/queue/errors").permitAll() - .simpDestMatchers("/admin/**").hasRole("ADMIN") -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - ----- -==== - -should change to: - -==== -.Java -[source,java,role="primary"] ----- -@Override -protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { - messages - .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() - .simpDestMatchers("/user/queue/errors").permitAll() - .simpDestMatchers("/admin/**").hasRole("ADMIN") - .anyMessage().denyAll(); -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { - messages - .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() - .simpDestMatchers("/user/queue/errors").permitAll() - .simpDestMatchers("/admin/**").hasRole("ADMIN") - .anyMessage().denyAll() -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - - - - ----- -==== - -==== Add `@EnableWebSocketSecurity` - -[NOTE] -==== -If you want to have CSRF disabled and you are using Java configuration, the migration steps are slightly different. -Instead of using `@EnableWebSocketSecurity`, you will override the appropriate methods in `WebSocketMessageBrokerConfigurer` yourself. -Please see xref:servlet/integrations/websocket.adoc#websocket-sameorigin-disable[the reference manual] for details about this step. -==== - -If you are using Java Configuration, add {security-api-url}org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.html[`@EnableWebSocketSecurity`] to your application. - -For example, you can add it to your websocket security configuration class, like so: - -==== -.Java -[source,java,role="primary"] ----- -@EnableWebSocketSecurity -@Configuration -public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { - // ... -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@EnableWebSocketSecurity -@Configuration -class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() { - // ... -} ----- -==== - -This will make a prototype instance of `MessageMatcherDelegatingAuthorizationManager.Builder` available to encourage configuration by composition instead of extension. - -==== Use an `AuthorizationManager>` instance - -To start using `AuthorizationManager`, you can set the `use-authorization-manager` attribute in XML or you can publish an `AuthorizationManager>` `@Bean` in Java. - -For example, the following application configuration: - -==== -.Java -[source,java,role="primary"] ----- -@Override -protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { - messages - .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() - .simpDestMatchers("/user/queue/errors").permitAll() - .simpDestMatchers("/admin/**").hasRole("ADMIN") - .anyMessage().denyAll(); -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { - messages - .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() - .simpDestMatchers("/user/queue/errors").permitAll() - .simpDestMatchers("/admin/**").hasRole("ADMIN") - .anyMessage().denyAll() -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - - - - ----- -==== - -changes to: - -==== -.Java -[source,java,role="primary"] ----- -@Bean -AuthorizationManager> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) { - messages - .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() - .simpDestMatchers("/user/queue/errors").permitAll() - .simpDestMatchers("/admin/**").hasRole("ADMIN") - .anyMessage().denyAll(); - return messages.build(); -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@Bean -fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager> { - messages - .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() - .simpDestMatchers("/user/queue/errors").permitAll() - .simpDestMatchers("/admin/**").hasRole("ADMIN") - .anyMessage().denyAll() - return messages.build() -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - - - - ----- -==== - -==== Stop Implementing `AbstractSecurityWebSocketMessageBrokerConfigurer` - -If you are using Java configuration, you can now simply extend `WebSocketMessageBrokerConfigurer`. - -For example, if your class that extends `AbstractSecurityWebSocketMessageBrokerConfigurer` is called `WebSocketSecurityConfig`, then: - -==== -.Java -[source,java,role="primary"] ----- -@EnableWebSocketSecurity -@Configuration -public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { - // ... -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@EnableWebSocketSecurity -@Configuration -class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() { - // ... -} ----- -==== - -changes to: - -==== -.Java -[source,java,role="primary"] ----- -@EnableWebSocketSecurity -@Configuration -public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer { - // ... -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@EnableWebSocketSecurity -@Configuration -class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer { - // ... -} ----- -==== - -[[servlet-authorizationmanager-messages-opt-out]] -==== Opt-out Steps - -In case you had trouble, take a look at these scenarios for optimal opt out behavior: - -===== I cannot declare an authorization rule for all requests - -If you are having trouble setting an `anyRequest` authorization rule of `denyAll`, please use {security-api-url}org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.Builder.Constraint.html#permitAll()[`permitAll`] instead, like so: - -==== -.Java -[source,java,role="primary"] ----- -@Bean -AuthorizationManager> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) { - messages - .simpDestMatchers("/user/queue/errors").permitAll() - .simpDestMatchers("/admin/**").hasRole("ADMIN") - // ... - .anyMessage().permitAll(); - return messages.build(); -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@Bean -fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager> { - messages - .simpDestMatchers("/user/queue/errors").permitAll() - .simpDestMatchers("/admin/**").hasRole("ADMIN") - // ... - .anyMessage().permitAll(); - return messages.build() -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - - ----- -==== - -===== I cannot get CSRF working, need some other `AbstractSecurityWebSocketMessageBrokerConfigurer` feature, or am having trouble with `AuthorizationManager` - -In the case of Java, you may continue using `AbstractMessageSecurityWebSocketMessageBrokerConfigurer`. -Even though it is deprecated, it will not be removed in 6.0. - -In the case of XML, you can opt out of `AuthorizationManager` by setting `use-authorization-manager="false"`: - -==== -.Xml -[source,xml,role="secondary"] ----- - - - - ----- -==== - -to: - -==== -.Xml -[source,xml,role="secondary"] ----- - - - - ----- -==== - -=== Use `AuthorizationManager` for Request Security - -xref:servlet/authorization/authorize-requests.adoc[HTTP Request Security] has been xref:servlet/authorization/authorize-http-requests.adoc[simplified] through {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[the `AuthorizationManager` API]. - -Should you run into trouble with making these changes, you can follow the <> at the end of this section. - -==== Ensure that all requests have defined authorization rules - -In Spring Security 5.8 and earlier, requests with no authorization rule are permitted by default. -It is a stronger security position to deny by default, thus requiring that authorization rules be clearly defined for every endpoint. -As such, in 6.0, Spring Security by default denies any request that is missing an authorization rule. - -The simplest way to prepare for this change is to introduce an appropriate {security-api-url}org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.html#anyRequest()[`anyRequest`] rule as the last authorization rule. -The recommendation is {security-api-url}org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.AuthorizedUrl.html#denyAll()[`denyAll`] since that is the implied 6.0 default. - -[NOTE] -==== -You may already have an `anyRequest` rule defined that you are happy with in which case this step can be skipped. -==== - -Adding `denyAll` to the end looks like changing: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeRequests((authorize) -> authorize - .filterSecurityInterceptorOncePerRequest(true) - .mvcMatchers("/app/**").hasRole("APP") - // ... - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeRequests { - filterSecurityInterceptorOncePerRequest = true - authorize("/app/**", hasRole("APP")) - // ... - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - ----- -==== - -to: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeRequests((authorize) -> authorize - .filterSecurityInterceptorOncePerRequest(true) - .mvcMatchers("/app/**").hasRole("APP") - // ... - .anyRequest().denyAll() - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeRequests { - filterSecurityInterceptorOncePerRequest = true - authorize("/app/**", hasRole("APP")) - // ... - authorize(anyRequest, denyAll) - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - ----- -==== - -If you have already migrated to `authorizeHttpRequests`, the recommended change is the same. - -==== Switch to `AuthorizationManager` - -To opt in to using `AuthorizationManager`, you can use `authorizeHttpRequests` or xref:servlet/appendix/namespace/http.adoc#nsa-http-use-authorization-manager[`use-authorization-manager`] for Java or XML, respectively. - -Change: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeRequests((authorize) -> authorize - .filterSecurityInterceptorOncePerRequest(true) - .mvcMatchers("/app/**").hasRole("APP") - // ... - .anyRequest().denyAll() - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeRequests { - filterSecurityInterceptorOncePerRequest = true - authorize("/app/**", hasRole("APP")) - // ... - authorize(anyRequest, denyAll) - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - ----- -==== - -to: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize - .shouldFilterAllDispatcherTypes(false) - .mvcMatchers("/app/**").hasRole("APP") - // ... - .anyRequest().denyAll() - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeHttpRequests { - shouldFilterAllDispatcherTypes = false - authorize("/app/**", hasRole("APP")) - // ... - authorize(anyRequest, denyAll) - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - ----- -==== - -==== Migrate SpEL expressions to `AuthorizationManager` - -For authorization rules, Java tends to be easier to test and maintain than SpEL. -As such, `authorizeHttpRequests` does not have a method for declaring a `String` SpEL. - -Instead, you can implement your own `AuthorizationManager` implementation or use `WebExpressionAuthorizationManager`. - -For completeness, both options will be demonstrated. - -First, if you have the following SpEL: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeRequests((authorize) -> authorize - .filterSecurityInterceptorOncePerRequest(true) - .mvcMatchers("/complicated/**").access("hasRole('ADMIN') || hasAuthority('SCOPE_read')") - // ... - .anyRequest().denyAll() - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeRequests { - filterSecurityInterceptorOncePerRequest = true - authorize("/complicated/**", access("hasRole('ADMIN') || hasAuthority('SCOPE_read')")) - // ... - authorize(anyRequest, denyAll) - } -} ----- -==== - -Then you can compose your own `AuthorizationManager` with Spring Security authorization primitives like so: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize - .shouldFilterAllDispatcherTypes(false) - .mvcMatchers("/complicated/**").access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read")) - // ... - .anyRequest().denyAll() - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeHttpRequests { - shouldFilterAllDispatcherTypes = false - authorize("/complicated/**", access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read")) - // ... - authorize(anyRequest, denyAll) - } -} ----- -==== - -Or you can use `WebExpressionAuthorizationManager` in the following way: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeRequests((authorize) -> authorize - .filterSecurityInterceptorOncePerRequest(true) - .mvcMatchers("/complicated/**").access( - new WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')") - ) - // ... - .anyRequest().denyAll() - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeRequests { - filterSecurityInterceptorOncePerRequest = true - authorize("/complicated/**", access( - WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')")) - ) - // ... - authorize(anyRequest, denyAll) - } -} ----- -==== - -[[switch-filter-all-dispatcher-types]] -==== Switch to filter all dispatcher types - -Spring Security 5.8 and earlier only xref:servlet/authorization/architecture.adoc[perform authorization] once per request. -This means that dispatcher types like `FORWARD` and `INCLUDE` that run after `REQUEST` are not secured by default. - -It's recommended that Spring Security secure all dispatch types. -As such, in 6.0, Spring Security changes this default. - -So, finally, change your authorization rules to filter all dispatcher types. - -To do this, you should change: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize - .shouldFilterAllDispatcherTypes(false) - .mvcMatchers("/app/**").hasRole("APP") - // ... - .anyRequest().denyAll() - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeHttpRequests { - shouldFilterAllDispatcherTypes = false - authorize("/app/**", hasRole("APP")) - // ... - authorize(anyRequest, denyAll) - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - ----- -==== - -to: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize - .shouldFilterAllDispatcherTypes(true) - .mvcMatchers("/app/**").hasRole("APP") - // ... - .anyRequest().denyAll() - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeHttpRequests { - shouldFilterAllDispatcherTypes = true - authorize("/app/**", hasRole("APP")) - // ... - authorize(anyRequest, denyAll) - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - ----- -==== - -And, the `FilterChainProxy` should be registered for all dispatcher types as well. -If you are using Spring Boot, https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.security.spring.security.filter.dispatcher-types[you have to change the `spring.security.filter.dispatcher-types` property] to include all dispatcher types: - -==== -.application.properties -[source,properties,role="primary"] ----- -spring.security.filter.dispatcher-types=request,async,error,forward,include ----- -==== - -If you are xref::servlet/configuration/java.adoc#_abstractsecuritywebapplicationinitializer[using the `AbstractSecurityWebApplicationInitializer`] you should override the `getSecurityDispatcherTypes` method and return all dispatcher types: - -==== -.Java -[source,java,role="primary"] ----- -import org.springframework.security.web.context.*; - -public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { - - @Override - protected EnumSet getSecurityDispatcherTypes() { - return EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.FORWARD, - DispatcherType.FORWARD, DispatcherType.INCLUDE); - } - -} ----- -==== - -===== Permit `FORWARD` when using Spring MVC - -If you are using {spring-framework-reference-url}/web.html#mvc-viewresolver[Spring MVC to resolve view names], you will need to permit `FORWARD` requests. -This is because when Spring MVC detects a mapping between view name and the actual views, it will perform a forward to the view. -As we saw on the <>, Spring Security 6.0 will apply authorization to `FORWARD` requests by default. - -Consider the following common configuration: - -==== -.Java -[source,java,role="primary"] ----- -@Bean -public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests((authorize) -> authorize - .shouldFilterAllDispatcherTypes(true) - .requestMatchers("/").authenticated() - .anyRequest().denyAll() - ) - .formLogin((form) -> form - .loginPage("/login") - .permitAll() - )); - return http.build(); -} ----- -==== - -and one of the following equivalents MVC view mapping configurations: - -==== -.Java -[source,java,role="primary"] ----- -@Controller -public class MyController { - - @GetMapping("/login") - public String login() { - return "login"; - } - -} ----- -==== - -==== -.Java -[source,java,role="primary"] ----- -@Configuration -public class MyWebMvcConfigurer implements WebMvcConfigurer { - - @Override - public void addViewControllers(ViewControllerRegistry registry) { - registry.addViewController("/login").setViewName("login"); - } - -} ----- -==== - -With either configuration, when there is a request to `/login`, Spring MVC will perform a *forward* to the view `login`, which, with the default configuration, is under `src/main/resources/templates/login.html` path. -The security configuration permits requests to `/login` but every other request will be denied, including the `FORWARD` request to the view under `/templates/login.html`. - -To fix this, you should configure Spring Security to permit `FORWARD` requests: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize - .shouldFilterAllDispatcherTypes(true) - .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll() - .anyRequest().denyAll() - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeHttpRequests { - shouldFilterAllDispatcherTypes = true - authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll) - authorize(anyRequest, denyAll) - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - - - - - ----- -==== - -==== Replace any custom filter-security ``AccessDecisionManager``s - -Your application may have a custom {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] or {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] arrangement. -The preparation strategy will depend on your reason for each arrangement. -Read on to find the best match for your situation. - -===== I use `UnanimousBased` - -If your application uses {security-api-url}org/springframework/security/access/vote/UnanimousBased.html[`UnanimousBased`], you should first adapt or replace any ``AccessDecisionVoter``s and then you can construct an `AuthorizationManager` like so: - -==== -.Java -[source,java,role="primary"] ----- -@Bean -AuthorizationManager requestAuthorization() { - PolicyAuthorizationManager policy = ...; - LocalAuthorizationManager local = ...; - return AuthorizationMangers.allOf(policy, local); -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@Bean -fun requestAuthorization(): AuthorizationManager { - val policy: PolicyAuthorizationManager = ... - val local: LocalAuthorizationManager = ... - return AuthorizationMangers.allOf(policy, local) -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - - - - ----- -==== - -then, wire it into the DSL like so: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization)) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeHttpRequests { - authorize(anyRequest, requestAuthorization) - } - // ... -} ----- - -.Xml -[source,xml,role="secondary"] ----- - ----- -==== - -[NOTE] -==== -`authorizeHttpRequests` is designed so that you can apply a custom `AuthorizationManager` to any url pattern. -See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[the reference] for more details. -==== - -===== I use `AffirmativeBased` - -If your application uses {security-api-url}org/springframework/security/access/vote/AffirmativeBased.html[`AffirmativeBased`], then you can construct an equivalent {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`], like so: - -==== -.Java -[source,java,role="primary"] ----- -@Bean -AuthorizationManager requestAuthorization() { - PolicyAuthorizationManager policy = ...; - LocalAuthorizationManager local = ...; - return AuthorizationMangers.anyOf(policy, local); -} ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -@Bean -fun requestAuthorization(): AuthorizationManager { - val policy: PolicyAuthorizationManager = ... - val local: LocalAuthorizationManager = ... - return AuthorizationMangers.anyOf(policy, local) -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - - - - ----- -==== - -then, wire it into the DSL like so: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization)) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeHttpRequests { - authorize(anyRequest, requestAuthorization) - } - // ... -} ----- - -.Xml -[source,xml,role="secondary"] ----- - ----- -==== - -[NOTE] -==== -`authorizeHttpRequests` is designed so that you can apply a custom `AuthorizationManager` to any url pattern. -See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[the reference] for more details. -==== - -===== I use `ConsensusBased` - -There is no framework-provided equivalent for {security-api-url}org/springframework/security/access/vote/ConsensusBased.html[`ConsensusBased`]. -In that case, please implement a composite {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] that takes the set of delegate ``AuthorizationManager``s into account. - -Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[adding a custom `AuthorizationManager`]. - -===== I use a custom `AccessDecisionVoter` - -You should either change the class to implement {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] or create an adapter. - - -Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution. -By way of example, though, here is what adapting {security-api-url}org/springframework/security/access/SecurityMetadataSource.html[`SecurityMetadataSource`] and {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] for `anyRequest().authenticated()` would look like: - -==== -.Java -[source,java,role="primary"] ----- -public final class AnyRequestAuthenticatedAuthorizationManagerAdapter implements AuthorizationManager { - private final SecurityMetadataSource metadata; - private final AccessDecisionVoter voter; - - public PreAuthorizeAuthorizationManagerAdapter(SecurityExpressionHandler expressionHandler) { - Map> requestMap = Collections.singletonMap( - AnyRequestMatcher.INSTANCE, Collections.singletonList(new SecurityConfig("authenticated"))); - this.metadata = new DefaultFilterInvocationSecurityMetadataSource(requestMap); - WebExpressionVoter voter = new WebExpressionVoter(); - voter.setExpressionHandler(expressionHandler); - this.voter = voter; - } - - public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext context) { - List attributes = this.metadata.getAttributes(context); - int decision = this.voter.vote(authentication.get(), invocation, attributes); - if (decision == ACCESS_GRANTED) { - return new AuthorizationDecision(true); - } - if (decision == ACCESS_DENIED) { - return new AuthorizationDecision(false); - } - return null; // abstain - } -} ----- -==== - -Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[adding a custom `AuthorizationManager`]. - -[[servlet-authorizationmanager-requests-opt-out]] -==== Opt-out Steps - -In case you had trouble, take a look at these scenarios for optimal opt out behavior: - -===== I cannot secure all dispatcher types - -If you cannot secure all dispatcher types, first try and declare which dispatcher types should not require authorization like so: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize - .shouldFilterAllDispatcherTypes(true) - .dispatcherTypeMatchers(FORWARD, INCLUDE).permitAll() - .mvcMatchers("/app/**").hasRole("APP") - // ... - .anyRequest().denyAll() - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeHttpRequests { - shouldFilterAllDispatcherTypes = true - authorize(DispatcherTypeRequestMatcher(FORWARD, INCLUDE), permitAll) - authorize("/app/**", hasRole("APP")) - // ... - authorize(anyRequest, denyAll) - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - - - - - - - FORWARD - INCLUDE - - - ----- -==== - -Or, if that doesn't work, then you can explicitly opt out of the behavior by setting `filter-all-dispatcher-types` and `filterAllDispatcherTypes` to `false`: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize - .filterAllDispatcherTypes(false) - .mvcMatchers("/app/**").hasRole("APP") - // ... - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeHttpRequests { - filterAllDispatcherTypes = false - authorize("/messages/**", hasRole("APP")) - // ... - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - ----- -==== - -or, if you are still using `authorizeRequests` or `use-authorization-manager="false"`, set `oncePerRequest` to `true`: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeRequests((authorize) -> authorize - .filterSecurityInterceptorOncePerRequest(true) - .mvcMatchers("/app/**").hasRole("APP") - // ... - ) - // ... ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeRequests { - filterSecurityInterceptorOncePerRequest = true - authorize("/messages/**", hasRole("APP")) - // ... - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - ----- -==== - -===== I cannot declare an authorization rule for all requests - -If you are having trouble setting an `anyRequest` authorization rule of `denyAll`, please use {security-api-url}org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.AuthorizedUrl.html#permitAll()[`permitAll`] instead, like so: - -==== -.Java -[source,java,role="primary"] ----- -http - .authorizeHttpReqeusts((authorize) -> authorize - .mvcMatchers("/app/*").hasRole("APP") - // ... - .anyRequest().permitAll() - ) ----- - -.Kotlin -[source,kotlin,role="secondary"] ----- -http { - authorizeHttpRequests { - authorize("/app*", hasRole("APP")) - // ... - authorize(anyRequest, permitAll) - } -} ----- - -.Xml -[source,xml,role="secondary"] ----- - - - - - ----- -==== - -===== I cannot migrate my SpEL or my `AccessDecisionManager` - -If you are having trouble with SpEL, `AccessDecisionManager`, or there is some other feature that you are needing to keep using in `` or `authorizeRequests`, try the following. - -First, if you still need `authorizeRequests`, you are welcome to keep using it. Even though it is deprecated, it is not removed in 6.0. - -Second, if you still need your custom `access-decision-manager-ref` or have some other reason to opt out of `AuthorizationManager`, do: - -==== -.Xml -[source,xml,role="secondary"] ----- - - - - ----- -==== - -=== 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"] ----- - - - - - - - - ----- -==== - -[[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"] ----- - - - - - - - - ----- -==== - -[[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"] ----- - - - - - - - - - - ----- -==== - -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 <>) 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"] ----- - - - - - - - - - - ----- -==== - -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"] ----- - - - - - - - - - - ----- -==== - -=== 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` 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 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 = 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 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 = 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]. - -=== 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"] ----- - - - org.opensaml - opensaml-core - 4.2.1 - - - org.opensaml - opensaml-saml-api - 4.2.1 - - - org.opensaml - opensaml-saml-impl - 4.2.1 - - ----- - -.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 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 authorities = myAuthoritiesExtractor.convert(assertion); - return new Saml2Authentication(principal, authentication.getSaml2Response(), authorities); -}); -Converter 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) ----- -==== - -[[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 <> 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 `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]. - -=== 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 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"] ----- - - - ----- -==== - -[[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 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"] ----- - - - ----- -==== - -=== 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 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`]. - -==== 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") - } - -} ----- -==== - -=== 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 -} ----- -==== - -=== 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(...)`. - -== Reactive - -=== 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 -<> 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`. diff --git a/docs/modules/ROOT/pages/migration/index.adoc b/docs/modules/ROOT/pages/migration/index.adoc new file mode 100644 index 0000000000..fe3cabfec0 --- /dev/null +++ b/docs/modules/ROOT/pages/migration/index.adoc @@ -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. diff --git a/docs/modules/ROOT/pages/migration/reactive.adoc b/docs/modules/ROOT/pages/migration/reactive.adoc new file mode 100644 index 0000000000..7f0e03a004 --- /dev/null +++ b/docs/modules/ROOT/pages/migration/reactive.adoc @@ -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 +<> 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`. diff --git a/docs/modules/ROOT/pages/migration/servlet/authentication.adoc b/docs/modules/ROOT/pages/migration/servlet/authentication.adoc new file mode 100644 index 0000000000..5fddab930a --- /dev/null +++ b/docs/modules/ROOT/pages/migration/servlet/authentication.adoc @@ -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"] +---- + + + + + + + + + + +---- +==== + +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 <>) 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"] +---- + + + + + + + + + + +---- +==== + +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"] +---- + + + + + + + + + + +---- +==== + +== 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"] +---- + + + + + + + + +---- +==== + +[[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"] +---- + + + + + + + + +---- +==== diff --git a/docs/modules/ROOT/pages/migration/servlet/authorization.adoc b/docs/modules/ROOT/pages/migration/servlet/authorization.adoc new file mode 100644 index 0000000000..f14a7eb899 --- /dev/null +++ b/docs/modules/ROOT/pages/migration/servlet/authorization.adoc @@ -0,0 +1,1559 @@ += Authorization Migrations + +The following steps relate to changes around how authorization is performed. + +== Use `AuthorizationManager` for Method Security + +xref:servlet/authorization/method-security.adoc[Method Security] has been xref:servlet/authorization/method-security.adoc#jc-enable-method-security[simplified] 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, note that `@EnableGlobalMethodSecurity`, while deprecated, will not be removed in 6.0, allowing you to opt out by sticking with the old annotation. + +[[servlet-replace-globalmethodsecurity-with-methodsecurity]] +=== Replace xref:servlet/authorization/method-security.adoc#jc-enable-global-method-security[global method security] with xref:servlet/authorization/method-security.adoc#jc-enable-method-security[method security] + +{security-api-url}org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.html[`@EnableGlobalMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-global-method-security[``] are deprecated in favor of {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.html[`@EnableMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-method-security[``], respectively. +The new annotation and XML element activate Spring's xref:servlet/authorization/method-security.adoc#jc-enable-method-security[pre-post annotations] by default and use `AuthorizationManager` internally. + +This means that the following two listings are functionally equivalent: + +==== +.Java +[source,java,role="primary"] +---- +@EnableGlobalMethodSecurity(prePostEnabled = true) +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableGlobalMethodSecurity(prePostEnabled = true) +---- + +.Xml +[source,xml,role="secondary"] +---- + +---- +==== + +and: + +==== +.Java +[source,java,role="primary"] +---- +@EnableMethodSecurity +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableMethodSecurity +---- + +.Xml +[source,xml,role="secondary"] +---- + +---- +==== + +For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior. + +For example, a listing like: + +==== +.Java +[source,java,role="primary"] +---- +@EnableGlobalMethodSecurity(securedEnabled = true) +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableGlobalMethodSecurity(securedEnabled = true) +---- + +.Xml +[source,xml,role="secondary"] +---- + +---- +==== + +should change to: + +==== +.Java +[source,java,role="primary"] +---- +@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false) +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false) +---- + +.Xml +[source,xml,role="secondary"] +---- + +---- +==== + +[[servlet-replace-permissionevaluator-bean-with-methodsecurityexpression-handler]] +=== Publish a `MethodSecurityExpressionHandler` instead of a `PermissionEvaluator` + +`@EnableMethodSecurity` does not pick up a `PermissionEvaluator`. +This helps keep its API simple. + +If you have a custom {security-api-url}org/springframework/security/access/PermissionEvaluator.html[`PermissionEvaluator`] `@Bean`, please change it from: + +==== +.Java +[source,java,role="primary"] +---- +@Bean +static PermissionEvaluator permissionEvaluator() { + // ... your evaluator +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +companion object { + @Bean + fun permissionEvaluator(): PermissionEvaluator { + // ... your evaluator + } +} +---- +==== + +to: + +==== +.Java +[source,java,role="primary"] +---- +@Bean +static MethodSecurityExpressionHandler expressionHandler() { + var expressionHandler = new DefaultMethodSecurityExpressionHandler(); + expressionHandler.setPermissionEvaluator(myPermissionEvaluator); + return expressionHandler; +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +companion object { + @Bean + fun expressionHandler(): MethodSecurityExpressionHandler { + val expressionHandler = DefaultMethodSecurityExpressionHandler + expressionHandler.setPermissionEvaluator(myPermissionEvaluator) + return expressionHandler + } +} +---- +==== + +=== Replace any custom method-security ``AccessDecisionManager``s + +Your application may have a custom {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] or {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] arrangement. +The preparation strategy will depend on your reason for each arrangement. +Read on to find the best match for your situation. + +==== I use `UnanimousBased` + +If your application uses {security-api-url}org/springframework/security/access/vote/UnanimousBased.html[`UnanimousBased`] with the default voters, you likely need do nothing since unanimous-based is the default behavior with {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.html[`@EnableMethodSecurity`]. + +However, if you do discover that you cannot accept the default authorization managers, you can use `AuthorizationManagers.allOf` to compose your own arrangement. +Having done that, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. + +==== I use `AffirmativeBased` + +If your application uses {security-api-url}org/springframework/security/access/vote/AffirmativeBased.html[`AffirmativeBased`], then you can construct an equivalent {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`], like so: + +==== +.Java +[source,java,role="primary"] +---- +AuthorizationManager authorization = AuthorizationManagers.anyOf( + // ... your list of authorization managers +) +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +val authorization = AuthorizationManagers.anyOf( + // ... your list of authorization managers +) +---- +==== + +Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. + +==== I use `ConsensusBased` + +There is no framework-provided equivalent for {security-api-url}org/springframework/security/access/vote/ConsensusBased.html[`ConsensusBased`]. +In that case, please implement a composite {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] that takes the set of delegate ``AuthorizationManager``s into account. + +Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. + +==== I use a custom `AccessDecisionVoter` + +You should either change the class to implement {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] or create an adapter. + +Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution. +By way of example, though, here is what adapting {security-api-url}org/springframework/security/access/SecurityMetadataSource.html[`SecurityMetadataSource`] and {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] for `@PreAuthorize` would look like: + +==== +.Java +[source,java,role="primary"] +---- +public final class PreAuthorizeAuthorizationManagerAdapter implements AuthorizationManager { + private final SecurityMetadataSource metadata; + private final AccessDecisionVoter voter; + + public PreAuthorizeAuthorizationManagerAdapter(MethodSecurityExpressionHandler expressionHandler) { + ExpressionBasedAnnotationAttributeFactory attributeFactory = + new ExpressionBasedAnnotationAttributeFactory(expressionHandler); + this.metadata = new PrePostAnnotationSecurityMetadataSource(attributeFactory); + ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice(); + expressionAdvice.setExpressionHandler(expressionHandler); + this.voter = new PreInvocationAuthorizationAdviceVoter(expressionAdvice); + } + + public AuthorizationDecision check(Supplier authentication, MethodInvocation invocation) { + List attributes = this.metadata.getAttributes(invocation, AopUtils.getTargetClass(invocation.getThis())); + int decision = this.voter.vote(authentication.get(), invocation, attributes); + if (decision == ACCESS_GRANTED) { + return new AuthorizationDecision(true); + } + if (decision == ACCESS_DENIED) { + return new AuthorizationDecision(false); + } + return null; // abstain + } +} +---- +==== + +Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. + +==== I use a custom `AfterInvocationManager` + +{security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] replaces both {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] and {security-api-url}org/springframework/security/access/intercept/AfterInvocationManager.html[`AfterInvocationManager`]. +The difference is that `AuthorizationManager` replaces `AccessDecisionManager` and `AuthorizationManager` replaces `AfterInvocationManager`. + +Given that, <<_i_use_a_custom_accessdecisionvoter,the same rules apply for adaptation>>, where the goal this time is to implement `AuthorizationManager` instead of `AuthorizationManager` and use `AuthorizationManagerAfterMethodInterceptor` instead of `AuthorizationManagerBeforeMethodInterceptor`. + +==== I use `RunAsManager` + +There is currently https://github.com/spring-projects/spring-security/issues/11331[no replacement for `RunAsManager`] though one is being considered. + +It is quite straightforward to adapt a `RunAsManager`, though, to the `AuthorizationManager` API, if needed. + +Here is some pseudocode to get you started: + +==== +.Java +[source,java,role="primary"] +---- +public final class RunAsAuthorizationManagerAdapter implements AuthorizationManager { + private final RunAsManager runAs = new RunAsManagerImpl(); + private final SecurityMetadataSource metadata; + private final AuthorizationManager authorization; + + // ... constructor + + public AuthorizationDecision check(Supplier authentication, T object) { + Supplier wrapped = (auth) -> { + List attributes = this.metadata.getAttributes(object); + return this.runAs.buildRunAs(auth, object, attributes); + }; + return this.authorization.check(wrapped, object); + } +} +---- +==== + +Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. + +[[servlet-check-for-annotationconfigurationexceptions]] +=== Check for ``AnnotationConfigurationException``s + +`@EnableMethodSecurity` and `` activate stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations. +If after moving to either you see ``AnnotationConfigurationException``s in your logs, follow the instructions in the exception message to clean up your application's method security annotation usage. + +== Use `AuthorizationManager` for Message Security + +xref:servlet/integrations/websocket.adoc[Message Security] has been xref:servlet/integrations/websocket.adoc#websocket-configuration[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 <> at the end of this section. + +=== Ensure all messages have defined authorization rules + +The now-deprecated {security-api-url}org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.html[message security support] permits all messages by default. +xref:servlet/integrations/websocket.adoc[The new support] has the stronger default of denying all messages. + +To prepare for this, ensure that authorization rules exist are declared for every request. + +For example, an application configuration like: + +==== +.Java +[source,java,role="primary"] +---- +@Override +protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { + messages + .simpDestMatchers("/user/queue/errors").permitAll() + .simpDestMatchers("/admin/**").hasRole("ADMIN"); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { + messages + .simpDestMatchers("/user/queue/errors").permitAll() + .simpDestMatchers("/admin/**").hasRole("ADMIN") +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + +---- +==== + +should change to: + +==== +.Java +[source,java,role="primary"] +---- +@Override +protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { + messages + .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() + .simpDestMatchers("/user/queue/errors").permitAll() + .simpDestMatchers("/admin/**").hasRole("ADMIN") + .anyMessage().denyAll(); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { + messages + .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() + .simpDestMatchers("/user/queue/errors").permitAll() + .simpDestMatchers("/admin/**").hasRole("ADMIN") + .anyMessage().denyAll() +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + + +---- +==== + +=== Add `@EnableWebSocketSecurity` + +[NOTE] +==== +If you want to have CSRF disabled and you are using Java configuration, the migration steps are slightly different. +Instead of using `@EnableWebSocketSecurity`, you will override the appropriate methods in `WebSocketMessageBrokerConfigurer` yourself. +Please see xref:servlet/integrations/websocket.adoc#websocket-sameorigin-disable[the reference manual] for details about this step. +==== + +If you are using Java Configuration, add {security-api-url}org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.html[`@EnableWebSocketSecurity`] to your application. + +For example, you can add it to your websocket security configuration class, like so: + +==== +.Java +[source,java,role="primary"] +---- +@EnableWebSocketSecurity +@Configuration +public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { + // ... +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableWebSocketSecurity +@Configuration +class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() { + // ... +} +---- +==== + +This will make a prototype instance of `MessageMatcherDelegatingAuthorizationManager.Builder` available to encourage configuration by composition instead of extension. + +=== Use an `AuthorizationManager>` instance + +To start using `AuthorizationManager`, you can set the `use-authorization-manager` attribute in XML or you can publish an `AuthorizationManager>` `@Bean` in Java. + +For example, the following application configuration: + +==== +.Java +[source,java,role="primary"] +---- +@Override +protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { + messages + .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() + .simpDestMatchers("/user/queue/errors").permitAll() + .simpDestMatchers("/admin/**").hasRole("ADMIN") + .anyMessage().denyAll(); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { + messages + .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() + .simpDestMatchers("/user/queue/errors").permitAll() + .simpDestMatchers("/admin/**").hasRole("ADMIN") + .anyMessage().denyAll() +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + + +---- +==== + +changes to: + +==== +.Java +[source,java,role="primary"] +---- +@Bean +AuthorizationManager> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) { + messages + .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() + .simpDestMatchers("/user/queue/errors").permitAll() + .simpDestMatchers("/admin/**").hasRole("ADMIN") + .anyMessage().denyAll(); + return messages.build(); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@Bean +fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager> { + messages + .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll() + .simpDestMatchers("/user/queue/errors").permitAll() + .simpDestMatchers("/admin/**").hasRole("ADMIN") + .anyMessage().denyAll() + return messages.build() +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + + +---- +==== + +=== Stop Implementing `AbstractSecurityWebSocketMessageBrokerConfigurer` + +If you are using Java configuration, you can now simply extend `WebSocketMessageBrokerConfigurer`. + +For example, if your class that extends `AbstractSecurityWebSocketMessageBrokerConfigurer` is called `WebSocketSecurityConfig`, then: + +==== +.Java +[source,java,role="primary"] +---- +@EnableWebSocketSecurity +@Configuration +public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { + // ... +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableWebSocketSecurity +@Configuration +class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() { + // ... +} +---- +==== + +changes to: + +==== +.Java +[source,java,role="primary"] +---- +@EnableWebSocketSecurity +@Configuration +public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer { + // ... +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableWebSocketSecurity +@Configuration +class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer { + // ... +} +---- +==== + +[[servlet-authorizationmanager-messages-opt-out]] +=== Opt-out Steps + +In case you had trouble, take a look at these scenarios for optimal opt out behavior: + +==== I cannot declare an authorization rule for all requests + +If you are having trouble setting an `anyRequest` authorization rule of `denyAll`, please use {security-api-url}org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.Builder.Constraint.html#permitAll()[`permitAll`] instead, like so: + +==== +.Java +[source,java,role="primary"] +---- +@Bean +AuthorizationManager> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) { + messages + .simpDestMatchers("/user/queue/errors").permitAll() + .simpDestMatchers("/admin/**").hasRole("ADMIN") + // ... + .anyMessage().permitAll(); + return messages.build(); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@Bean +fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager> { + messages + .simpDestMatchers("/user/queue/errors").permitAll() + .simpDestMatchers("/admin/**").hasRole("ADMIN") + // ... + .anyMessage().permitAll(); + return messages.build() +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + +---- +==== + +==== I cannot get CSRF working, need some other `AbstractSecurityWebSocketMessageBrokerConfigurer` feature, or am having trouble with `AuthorizationManager` + +In the case of Java, you may continue using `AbstractMessageSecurityWebSocketMessageBrokerConfigurer`. +Even though it is deprecated, it will not be removed in 6.0. + +In the case of XML, you can opt out of `AuthorizationManager` by setting `use-authorization-manager="false"`: + +==== +.Xml +[source,xml,role="secondary"] +---- + + + + +---- +==== + +to: + +==== +.Xml +[source,xml,role="secondary"] +---- + + + + +---- +==== + +== Use `AuthorizationManager` for Request Security + +xref:servlet/authorization/authorize-requests.adoc[HTTP Request Security] has been xref:servlet/authorization/authorize-http-requests.adoc[simplified] through {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[the `AuthorizationManager` API]. + +Should you run into trouble with making these changes, you can follow the <> at the end of this section. + +=== Ensure that all requests have defined authorization rules + +In Spring Security 5.8 and earlier, requests with no authorization rule are permitted by default. +It is a stronger security position to deny by default, thus requiring that authorization rules be clearly defined for every endpoint. +As such, in 6.0, Spring Security by default denies any request that is missing an authorization rule. + +The simplest way to prepare for this change is to introduce an appropriate {security-api-url}org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.html#anyRequest()[`anyRequest`] rule as the last authorization rule. +The recommendation is {security-api-url}org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.AuthorizedUrl.html#denyAll()[`denyAll`] since that is the implied 6.0 default. + +[NOTE] +==== +You may already have an `anyRequest` rule defined that you are happy with in which case this step can be skipped. +==== + +Adding `denyAll` to the end looks like changing: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeRequests((authorize) -> authorize + .filterSecurityInterceptorOncePerRequest(true) + .mvcMatchers("/app/**").hasRole("APP") + // ... + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeRequests { + filterSecurityInterceptorOncePerRequest = true + authorize("/app/**", hasRole("APP")) + // ... + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + +---- +==== + +to: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeRequests((authorize) -> authorize + .filterSecurityInterceptorOncePerRequest(true) + .mvcMatchers("/app/**").hasRole("APP") + // ... + .anyRequest().denyAll() + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeRequests { + filterSecurityInterceptorOncePerRequest = true + authorize("/app/**", hasRole("APP")) + // ... + authorize(anyRequest, denyAll) + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + +---- +==== + +If you have already migrated to `authorizeHttpRequests`, the recommended change is the same. + +=== Switch to `AuthorizationManager` + +To opt in to using `AuthorizationManager`, you can use `authorizeHttpRequests` or xref:servlet/appendix/namespace/http.adoc#nsa-http-use-authorization-manager[`use-authorization-manager`] for Java or XML, respectively. + +Change: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeRequests((authorize) -> authorize + .filterSecurityInterceptorOncePerRequest(true) + .mvcMatchers("/app/**").hasRole("APP") + // ... + .anyRequest().denyAll() + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeRequests { + filterSecurityInterceptorOncePerRequest = true + authorize("/app/**", hasRole("APP")) + // ... + authorize(anyRequest, denyAll) + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + +---- +==== + +to: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeHttpRequests((authorize) -> authorize + .shouldFilterAllDispatcherTypes(false) + .mvcMatchers("/app/**").hasRole("APP") + // ... + .anyRequest().denyAll() + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeHttpRequests { + shouldFilterAllDispatcherTypes = false + authorize("/app/**", hasRole("APP")) + // ... + authorize(anyRequest, denyAll) + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + +---- +==== + +=== Migrate SpEL expressions to `AuthorizationManager` + +For authorization rules, Java tends to be easier to test and maintain than SpEL. +As such, `authorizeHttpRequests` does not have a method for declaring a `String` SpEL. + +Instead, you can implement your own `AuthorizationManager` implementation or use `WebExpressionAuthorizationManager`. + +For completeness, both options will be demonstrated. + +First, if you have the following SpEL: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeRequests((authorize) -> authorize + .filterSecurityInterceptorOncePerRequest(true) + .mvcMatchers("/complicated/**").access("hasRole('ADMIN') || hasAuthority('SCOPE_read')") + // ... + .anyRequest().denyAll() + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeRequests { + filterSecurityInterceptorOncePerRequest = true + authorize("/complicated/**", access("hasRole('ADMIN') || hasAuthority('SCOPE_read')")) + // ... + authorize(anyRequest, denyAll) + } +} +---- +==== + +Then you can compose your own `AuthorizationManager` with Spring Security authorization primitives like so: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeHttpRequests((authorize) -> authorize + .shouldFilterAllDispatcherTypes(false) + .mvcMatchers("/complicated/**").access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read")) + // ... + .anyRequest().denyAll() + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeHttpRequests { + shouldFilterAllDispatcherTypes = false + authorize("/complicated/**", access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read")) + // ... + authorize(anyRequest, denyAll) + } +} +---- +==== + +Or you can use `WebExpressionAuthorizationManager` in the following way: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeRequests((authorize) -> authorize + .filterSecurityInterceptorOncePerRequest(true) + .mvcMatchers("/complicated/**").access( + new WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')") + ) + // ... + .anyRequest().denyAll() + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeRequests { + filterSecurityInterceptorOncePerRequest = true + authorize("/complicated/**", access( + WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')")) + ) + // ... + authorize(anyRequest, denyAll) + } +} +---- +==== + +[[switch-filter-all-dispatcher-types]] +=== Switch to filter all dispatcher types + +Spring Security 5.8 and earlier only xref:servlet/authorization/architecture.adoc[perform authorization] once per request. +This means that dispatcher types like `FORWARD` and `INCLUDE` that run after `REQUEST` are not secured by default. + +It's recommended that Spring Security secure all dispatch types. +As such, in 6.0, Spring Security changes this default. + +So, finally, change your authorization rules to filter all dispatcher types. + +To do this, you should change: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeHttpRequests((authorize) -> authorize + .shouldFilterAllDispatcherTypes(false) + .mvcMatchers("/app/**").hasRole("APP") + // ... + .anyRequest().denyAll() + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeHttpRequests { + shouldFilterAllDispatcherTypes = false + authorize("/app/**", hasRole("APP")) + // ... + authorize(anyRequest, denyAll) + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + +---- +==== + +to: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeHttpRequests((authorize) -> authorize + .shouldFilterAllDispatcherTypes(true) + .mvcMatchers("/app/**").hasRole("APP") + // ... + .anyRequest().denyAll() + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeHttpRequests { + shouldFilterAllDispatcherTypes = true + authorize("/app/**", hasRole("APP")) + // ... + authorize(anyRequest, denyAll) + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + +---- +==== + +And, the `FilterChainProxy` should be registered for all dispatcher types as well. +If you are using Spring Boot, https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.security.spring.security.filter.dispatcher-types[you have to change the `spring.security.filter.dispatcher-types` property] to include all dispatcher types: + +==== +.application.properties +[source,properties,role="primary"] +---- +spring.security.filter.dispatcher-types=request,async,error,forward,include +---- +==== + +If you are xref:servlet/configuration/java.adoc#_abstractsecuritywebapplicationinitializer[using the `AbstractSecurityWebApplicationInitializer`] you should override the `getSecurityDispatcherTypes` method and return all dispatcher types: + +==== +.Java +[source,java,role="primary"] +---- +import org.springframework.security.web.context.*; + +public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { + + @Override + protected EnumSet getSecurityDispatcherTypes() { + return EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.FORWARD, + DispatcherType.FORWARD, DispatcherType.INCLUDE); + } + +} +---- +==== + +==== Permit `FORWARD` when using Spring MVC + +If you are using {spring-framework-reference-url}/web.html#mvc-viewresolver[Spring MVC to resolve view names], you will need to permit `FORWARD` requests. +This is because when Spring MVC detects a mapping between view name and the actual views, it will perform a forward to the view. +As we saw on the <>, Spring Security 6.0 will apply authorization to `FORWARD` requests by default. + +Consider the following common configuration: + +==== +.Java +[source,java,role="primary"] +---- +@Bean +public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((authorize) -> authorize + .shouldFilterAllDispatcherTypes(true) + .requestMatchers("/").authenticated() + .anyRequest().denyAll() + ) + .formLogin((form) -> form + .loginPage("/login") + .permitAll() + )); + return http.build(); +} +---- +==== + +and one of the following equivalents MVC view mapping configurations: + +==== +.Java +[source,java,role="primary"] +---- +@Controller +public class MyController { + + @GetMapping("/login") + public String login() { + return "login"; + } + +} +---- +==== + +==== +.Java +[source,java,role="primary"] +---- +@Configuration +public class MyWebMvcConfigurer implements WebMvcConfigurer { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/login").setViewName("login"); + } + +} +---- +==== + +With either configuration, when there is a request to `/login`, Spring MVC will perform a *forward* to the view `login`, which, with the default configuration, is under `src/main/resources/templates/login.html` path. +The security configuration permits requests to `/login` but every other request will be denied, including the `FORWARD` request to the view under `/templates/login.html`. + +To fix this, you should configure Spring Security to permit `FORWARD` requests: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeHttpRequests((authorize) -> authorize + .shouldFilterAllDispatcherTypes(true) + .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll() + .anyRequest().denyAll() + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeHttpRequests { + shouldFilterAllDispatcherTypes = true + authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll) + authorize(anyRequest, denyAll) + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + + + +---- +==== + +=== Replace any custom filter-security ``AccessDecisionManager``s + +Your application may have a custom {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] or {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] arrangement. +The preparation strategy will depend on your reason for each arrangement. +Read on to find the best match for your situation. + +==== I use `UnanimousBased` + +If your application uses {security-api-url}org/springframework/security/access/vote/UnanimousBased.html[`UnanimousBased`], you should first adapt or replace any ``AccessDecisionVoter``s and then you can construct an `AuthorizationManager` like so: + +==== +.Java +[source,java,role="primary"] +---- +@Bean +AuthorizationManager requestAuthorization() { + PolicyAuthorizationManager policy = ...; + LocalAuthorizationManager local = ...; + return AuthorizationMangers.allOf(policy, local); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@Bean +fun requestAuthorization(): AuthorizationManager { + val policy: PolicyAuthorizationManager = ... + val local: LocalAuthorizationManager = ... + return AuthorizationMangers.allOf(policy, local) +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + + +---- +==== + +then, wire it into the DSL like so: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization)) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeHttpRequests { + authorize(anyRequest, requestAuthorization) + } + // ... +} +---- + +.Xml +[source,xml,role="secondary"] +---- + +---- +==== + +[NOTE] +==== +`authorizeHttpRequests` is designed so that you can apply a custom `AuthorizationManager` to any url pattern. +See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[the reference] for more details. +==== + +==== I use `AffirmativeBased` + +If your application uses {security-api-url}org/springframework/security/access/vote/AffirmativeBased.html[`AffirmativeBased`], then you can construct an equivalent {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`], like so: + +==== +.Java +[source,java,role="primary"] +---- +@Bean +AuthorizationManager requestAuthorization() { + PolicyAuthorizationManager policy = ...; + LocalAuthorizationManager local = ...; + return AuthorizationMangers.anyOf(policy, local); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@Bean +fun requestAuthorization(): AuthorizationManager { + val policy: PolicyAuthorizationManager = ... + val local: LocalAuthorizationManager = ... + return AuthorizationMangers.anyOf(policy, local) +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + + +---- +==== + +then, wire it into the DSL like so: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization)) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeHttpRequests { + authorize(anyRequest, requestAuthorization) + } + // ... +} +---- + +.Xml +[source,xml,role="secondary"] +---- + +---- +==== + +[NOTE] +==== +`authorizeHttpRequests` is designed so that you can apply a custom `AuthorizationManager` to any url pattern. +See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[the reference] for more details. +==== + +==== I use `ConsensusBased` + +There is no framework-provided equivalent for {security-api-url}org/springframework/security/access/vote/ConsensusBased.html[`ConsensusBased`]. +In that case, please implement a composite {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] that takes the set of delegate ``AuthorizationManager``s into account. + +Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[adding a custom `AuthorizationManager`]. + +==== I use a custom `AccessDecisionVoter` + +You should either change the class to implement {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] or create an adapter. + + +Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution. +By way of example, though, here is what adapting {security-api-url}org/springframework/security/access/SecurityMetadataSource.html[`SecurityMetadataSource`] and {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] for `anyRequest().authenticated()` would look like: + +==== +.Java +[source,java,role="primary"] +---- +public final class AnyRequestAuthenticatedAuthorizationManagerAdapter implements AuthorizationManager { + private final SecurityMetadataSource metadata; + private final AccessDecisionVoter voter; + + public PreAuthorizeAuthorizationManagerAdapter(SecurityExpressionHandler expressionHandler) { + Map> requestMap = Collections.singletonMap( + AnyRequestMatcher.INSTANCE, Collections.singletonList(new SecurityConfig("authenticated"))); + this.metadata = new DefaultFilterInvocationSecurityMetadataSource(requestMap); + WebExpressionVoter voter = new WebExpressionVoter(); + voter.setExpressionHandler(expressionHandler); + this.voter = voter; + } + + public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext context) { + List attributes = this.metadata.getAttributes(context); + int decision = this.voter.vote(authentication.get(), invocation, attributes); + if (decision == ACCESS_GRANTED) { + return new AuthorizationDecision(true); + } + if (decision == ACCESS_DENIED) { + return new AuthorizationDecision(false); + } + return null; // abstain + } +} +---- +==== + +Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[adding a custom `AuthorizationManager`]. + +[[servlet-authorizationmanager-requests-opt-out]] +=== Opt-out Steps + +In case you had trouble, take a look at these scenarios for optimal opt out behavior: + +==== I cannot secure all dispatcher types + +If you cannot secure all dispatcher types, first try and declare which dispatcher types should not require authorization like so: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeHttpRequests((authorize) -> authorize + .shouldFilterAllDispatcherTypes(true) + .dispatcherTypeMatchers(FORWARD, INCLUDE).permitAll() + .mvcMatchers("/app/**").hasRole("APP") + // ... + .anyRequest().denyAll() + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeHttpRequests { + shouldFilterAllDispatcherTypes = true + authorize(DispatcherTypeRequestMatcher(FORWARD, INCLUDE), permitAll) + authorize("/app/**", hasRole("APP")) + // ... + authorize(anyRequest, denyAll) + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + + + + + FORWARD + INCLUDE + + + +---- +==== + +Or, if that doesn't work, then you can explicitly opt out of the behavior by setting `filter-all-dispatcher-types` and `filterAllDispatcherTypes` to `false`: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeHttpRequests((authorize) -> authorize + .filterAllDispatcherTypes(false) + .mvcMatchers("/app/**").hasRole("APP") + // ... + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeHttpRequests { + filterAllDispatcherTypes = false + authorize("/messages/**", hasRole("APP")) + // ... + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + +---- +==== + +or, if you are still using `authorizeRequests` or `use-authorization-manager="false"`, set `oncePerRequest` to `true`: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeRequests((authorize) -> authorize + .filterSecurityInterceptorOncePerRequest(true) + .mvcMatchers("/app/**").hasRole("APP") + // ... + ) + // ... +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeRequests { + filterSecurityInterceptorOncePerRequest = true + authorize("/messages/**", hasRole("APP")) + // ... + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + +---- +==== + +==== I cannot declare an authorization rule for all requests + +If you are having trouble setting an `anyRequest` authorization rule of `denyAll`, please use {security-api-url}org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.AuthorizedUrl.html#permitAll()[`permitAll`] instead, like so: + +==== +.Java +[source,java,role="primary"] +---- +http + .authorizeHttpReqeusts((authorize) -> authorize + .mvcMatchers("/app/*").hasRole("APP") + // ... + .anyRequest().permitAll() + ) +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +http { + authorizeHttpRequests { + authorize("/app*", hasRole("APP")) + // ... + authorize(anyRequest, permitAll) + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + +---- +==== + +==== I cannot migrate my SpEL or my `AccessDecisionManager` + +If you are having trouble with SpEL, `AccessDecisionManager`, or there is some other feature that you are needing to keep using in `` or `authorizeRequests`, try the following. + +First, if you still need `authorizeRequests`, you are welcome to keep using it. Even though it is deprecated, it is not removed in 6.0. + +Second, if you still need your custom `access-decision-manager-ref` or have some other reason to opt out of `AuthorizationManager`, do: + +==== +.Xml +[source,xml,role="secondary"] +---- + + + + +---- +==== diff --git a/docs/modules/ROOT/pages/migration/servlet/config.adoc b/docs/modules/ROOT/pages/migration/servlet/config.adoc new file mode 100644 index 0000000000..83e8178a2f --- /dev/null +++ b/docs/modules/ROOT/pages/migration/servlet/config.adoc @@ -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 <> 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`]. diff --git a/docs/modules/ROOT/pages/migration/servlet/exploits.adoc b/docs/modules/ROOT/pages/migration/servlet/exploits.adoc new file mode 100644 index 0000000000..cf5cc5b1d9 --- /dev/null +++ b/docs/modules/ROOT/pages/migration/servlet/exploits.adoc @@ -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"] +---- + + + + + +---- +==== + +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"] +---- + + + + + + + + + +---- +==== + +== CSRF BREACH Protection + +If the steps for <> 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"] +---- + + + + + +---- +==== diff --git a/docs/modules/ROOT/pages/migration/servlet/index.adoc b/docs/modules/ROOT/pages/migration/servlet/index.adoc new file mode 100644 index 0000000000..adc75685a6 --- /dev/null +++ b/docs/modules/ROOT/pages/migration/servlet/index.adoc @@ -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. diff --git a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc new file mode 100644 index 0000000000..281dd6c5d7 --- /dev/null +++ b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc @@ -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 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"] +---- + + + +---- +==== + +[[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 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"] +---- + + + +---- +==== + +== 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(...)`. + diff --git a/docs/modules/ROOT/pages/migration/servlet/saml2.adoc b/docs/modules/ROOT/pages/migration/servlet/saml2.adoc new file mode 100644 index 0000000000..210968646c --- /dev/null +++ b/docs/modules/ROOT/pages/migration/servlet/saml2.adoc @@ -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"] +---- + + + org.opensaml + opensaml-core + 4.2.1 + + + org.opensaml + opensaml-saml-api + 4.2.1 + + + org.opensaml + opensaml-saml-impl + 4.2.1 + + +---- + +.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 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 authorities = myAuthoritiesExtractor.convert(assertion); + return new Saml2Authentication(principal, authentication.getSaml2Response(), authorities); +}); +Converter 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` 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 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 = 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 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 = 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]. diff --git a/docs/modules/ROOT/pages/migration/servlet/session-management.adoc b/docs/modules/ROOT/pages/migration/servlet/session-management.adoc new file mode 100644 index 0000000000..182c61aa11 --- /dev/null +++ b/docs/modules/ROOT/pages/migration/servlet/session-management.adoc @@ -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"] +---- + + + + + + + + + + + +---- +==== + +[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 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"] +---- + + + + +---- +==== + +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"] +---- + + + + +---- +====