You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
570 lines
18 KiB
570 lines
18 KiB
[[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::[] |
|
. |
|
|
|
== Servlet |
|
|
|
=== 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[] |
|
|
|
[[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. |
|
|
|
''' |
|
|
|
[[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[`<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[`<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"] |
|
---- |
|
<global-method-security pre-post-enabled="true"/> |
|
---- |
|
==== |
|
|
|
and: |
|
|
|
==== |
|
.Java |
|
[source,java,role="primary"] |
|
---- |
|
@EnableMethodSecurity |
|
---- |
|
|
|
.Kotlin |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@EnableMethodSecurity |
|
---- |
|
|
|
.Xml |
|
[source,xml,role="secondary"] |
|
---- |
|
<method-security/> |
|
---- |
|
==== |
|
|
|
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"] |
|
---- |
|
<global-method-security secured-enabled="true"/> |
|
---- |
|
==== |
|
|
|
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"] |
|
---- |
|
<method-security secured-enabled="true" pre-post-enabled="false"/> |
|
---- |
|
==== |
|
|
|
''' |
|
|
|
[[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 |
|
} |
|
} |
|
---- |
|
==== |
|
|
|
''' |
|
|
|
[[servlet-check-for-annotationconfigurationexceptions]] |
|
==== Check for ``AnnotationConfigurationException``s |
|
|
|
`@EnableMethodSecurity` and `<method-security>` 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. |
|
|
|
==== Declare the 5.8 default |
|
|
|
In case you run into trouble with the ensuing steps and cannot use `AuthorizationManager` at this time, it's recommended as a first step to declare you are using the 5.8 default so that 5.8 behavior is preserved when you update. |
|
|
|
The only default to change for Method Security is if you are using `<websocket-message-broker>` in which case you will change: |
|
|
|
==== |
|
.Xml |
|
[source,xml,role="secondary"] |
|
---- |
|
<websocket-message-broker> |
|
<intercept-message pattern="/user/queue/errors" access="permitAll"/> |
|
<intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/> |
|
</websocket-message-broker> |
|
---- |
|
==== |
|
|
|
to: |
|
|
|
==== |
|
.Xml |
|
[source,xml,role="secondary"] |
|
---- |
|
<websocket-message-broker use-authorization-manager="false"> |
|
<intercept-message pattern="/user/queue/errors" access="permitAll"/> |
|
<intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/> |
|
</websocket-message-broker> |
|
---- |
|
==== |
|
|
|
Later steps will turn this value back on, but now your code is minimally ready for upgrading in case you run into trouble with the remaining steps. |
|
|
|
==== 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"] |
|
---- |
|
<websocket-message-broker> |
|
<intercept-message pattern="/user/queue/errors" access="permitAll"/> |
|
<intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/> |
|
</websocket-message-broker> |
|
---- |
|
==== |
|
|
|
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"] |
|
---- |
|
<websocket-message-broker> |
|
<intercept-message type="CONNECT" access="permitAll"/> |
|
<intercept-message type="DISCONNECT" access="permitAll"/> |
|
<intercept-message type="UNSUBSCRIBE" access="permitAll"/> |
|
<intercept-message pattern="/user/queue/errors" access="permitAll"/> |
|
<intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/> |
|
<intercept-message pattern="/**" access="denyAll"/> |
|
</websocket-message-broker> |
|
---- |
|
==== |
|
|
|
==== 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<Message<?>>` instance |
|
|
|
To start using `AuthorizationManager`, you can set the `use-authorization-manager` attribute in XML or you can publish an `AuthorizationManager<Message<?>>` `@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"] |
|
---- |
|
<websocket-message-broker> |
|
<intercept-message type="CONNECT" access="permitAll"/> |
|
<intercept-message type="DISCONNECT" access="permitAll"/> |
|
<intercept-message type="UNSUBSCRIBE" access="permitAll"/> |
|
<intercept-message pattern="/user/queue/errors" access="permitAll"/> |
|
<intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/> |
|
<intercept-message pattern="/**" access="denyAll"/> |
|
</websocket-message-broker> |
|
---- |
|
==== |
|
|
|
changes to: |
|
|
|
==== |
|
.Java |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
AuthorizationManager<Message<?>> 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<Message<?>> { |
|
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"] |
|
---- |
|
<websocket-message-broker use-authorization-manager="true"> |
|
<intercept-message type="CONNECT" access="permitAll"/> |
|
<intercept-message type="DISCONNECT" access="permitAll"/> |
|
<intercept-message type="UNSUBSCRIBE" access="permitAll"/> |
|
<intercept-message pattern="/user/queue/errors" access="permitAll"/> |
|
<intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/> |
|
<intercept-message pattern="/**" access="denyAll"/> |
|
</websocket-message-broker> |
|
---- |
|
==== |
|
|
|
==== 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 { |
|
// ... |
|
} |
|
---- |
|
==== |
|
|
|
== 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. |
|
|
|
''' |
|
|
|
[[reactive-change-to-useauthorizationmanager]] |
|
==== Change `useAuthorizationManager` to `true` |
|
|
|
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. |
|
|
|
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) |
|
---- |
|
==== |
|
|
|
[NOTE] |
|
===== |
|
In 6.0, `useAuthorizationManager` defaults to `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.
|
|
|