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.
569 lines
16 KiB
569 lines
16 KiB
= Web Migrations |
|
|
|
== Favor Relative URIs |
|
|
|
When redirecting to a login endpoint, Spring Security has favored absolute URIs in the past. |
|
For example, if you set your login page like so: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
http |
|
// ... |
|
.formLogin((form) -> form.loginPage("/my-login")) |
|
// ... |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
http { |
|
formLogin { |
|
loginPage = "/my-login" |
|
} |
|
} |
|
---- |
|
|
|
Xml:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
<http ...> |
|
<form-login login-page="/my-login"/> |
|
</http> |
|
---- |
|
====== |
|
|
|
then when redirecting to `/my-login` Spring Security would use a `Location:` like the following: |
|
|
|
[source] |
|
---- |
|
302 Found |
|
// ... |
|
Location: https://myapp.example.org/my-login |
|
---- |
|
|
|
However, this is no longer necessary given that the RFC is was based on is now obsolete. |
|
|
|
In Spring Security 7, this is changed to use a relative URI like so: |
|
|
|
[source] |
|
---- |
|
302 Found |
|
// ... |
|
Location: /my-login |
|
---- |
|
|
|
Most applications will not notice a difference. |
|
However, in the event that this change causes problems, you can switch back to the Spring Security 6 behavior by setting the `favorRelativeUrls` value: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
LoginUrlAuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint("/my-login"); |
|
entryPoint.setFavorRelativeUris(false); |
|
http |
|
// ... |
|
.exceptionHandling((exceptions) -> exceptions.authenticaitonEntryPoint(entryPoint)) |
|
// ... |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
LoginUrlAuthenticationEntryPoint entryPoint = LoginUrlAuthenticationEntryPoint("/my-login") |
|
entryPoint.setFavorRelativeUris(false) |
|
|
|
http { |
|
exceptionHandling { |
|
authenticationEntryPoint = entryPoint |
|
} |
|
} |
|
---- |
|
|
|
Xml:: |
|
+ |
|
[source,xml,role="secondary"] |
|
---- |
|
<http entry-point-ref="myEntryPoint"> |
|
<!-- ... --> |
|
</http> |
|
|
|
<b:bean id="myEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> |
|
<b:property name="favorRelativeUris" value="true"/> |
|
</b:bean> |
|
---- |
|
====== |
|
|
|
== PortResolver |
|
|
|
Spring Security uses an API called `PortResolver` to provide a workaround for a bug in Internet Explorer. |
|
The workaround is no longer necessary and can cause users problems in some scenarios. |
|
For this reason, Spring Security 7 will remove the `PortResolver` interface. |
|
|
|
To prepare for this change, users should expose the `PortResolver.NO_OP` as a Bean named `portResolver`. |
|
This ensures that the `PortResolver` implementation that is used is a no-op (e.g. does nothing) which simulates the removal of `PortResolver`. |
|
An example configuration can be found below: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
PortResolver portResolver() { |
|
return PortResolver.NO_OP; |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Bean |
|
open fun portResolver(): PortResolver { |
|
return PortResolver.NO_OP |
|
} |
|
---- |
|
|
|
Xml:: |
|
+ |
|
[source,xml,role="secondary"] |
|
---- |
|
|
|
<util:constant id="portResolver" |
|
static-field="org.springframework.security.web.PortResolver.NO_OP"> |
|
---- |
|
====== |
|
|
|
[[use-path-pattern]] |
|
== Use PathPatternRequestMatcher by Default |
|
|
|
In Spring Security 7, `AntPathRequestMatcher` and `MvcRequestMatcher` are no longer supported and the Java DSL requires that all URIs be absolute (less any context root). |
|
At that time, Spring Security 7 will use `PathPatternRequestMatcher` by default. |
|
|
|
To check how prepared you are for this change, you can publish this bean: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { |
|
return new PathPatternRequestMatcherBuilderFactoryBean(); |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Bean |
|
fun requestMatcherBuilder(): PathPatternRequestMatcherBuilderFactoryBean { |
|
return PathPatternRequestMatcherBuilderFactoryBean() |
|
} |
|
---- |
|
|
|
Xml:: |
|
+ |
|
[source,xml,role="secondary"] |
|
---- |
|
<b:bean class="org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean"/> |
|
---- |
|
====== |
|
|
|
This will tell the Spring Security DSL to use `PathPatternRequestMatcher` for all request matchers that it constructs. |
|
|
|
In the event that you are directly constructing an object (as opposed to having the DSL construct it) that has a `setRequestMatcher` method. you should also proactively specify a `PathPatternRequestMatcher` there as well. |
|
|
|
=== Migrate `exitUserUrl` and `switchUserUrl` Request Matchers in `SwitchUserFilter` |
|
|
|
`SwitchUserFilter`, constructs an `AntPathRequestMatcher` in its `setExitUserUrl` and `setSwitchUserUrl` methods. |
|
This will change to use `PathPatternRequestMatcher` in Spring Security 7. |
|
|
|
To prepare for this change, call `setExitUserMatcher` and `setSwithcUserMatcher` to provide this `PathPatternRequestMatcher` in advance. |
|
That is, change this: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
SwitchUserFilter switchUser = new SwitchUserFilter(); |
|
// ... other configuration |
|
switchUser.setExitUserUrl("/exit/impersonate"); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
val switchUser = SwitchUserFilter() |
|
// ... other configuration |
|
switchUser.setExitUserUrl("/exit/impersonate") |
|
---- |
|
====== |
|
|
|
to this: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
SwitchUserFilter switchUser = new SwitchUserFilter(); |
|
// ... other configuration |
|
switchUser.setExitUserMatcher(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, "/exit/impersonate")); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
val switchUser = SwitchUserFilter() |
|
// ... other configuration |
|
switchUser.setExitUserMatcher(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, "/exit/impersonate")) |
|
---- |
|
====== |
|
|
|
=== Migrate `filterProcessingUrl` Request Matcher in `AbstractAuthenticationProcessingFilter` Implementations |
|
|
|
Spring Security 6 converts any processing endpoint configured through `setFilterProcessingUrl` to an `AntPathRequestMatcher`. |
|
In Spring Security 7, this will change to `PathPatternRequestMatcher`. |
|
|
|
If you are directly invoking `setFilterProcessingUrl` on a filter that extends `AbstractAuthenticationProcessingFilter`, like `UsernamePasswordAuthenticationFilter`, `OAuth2LoginAuthenticationFilter`, `Saml2WebSsoAuthenticationFilter`, `OneTimeTokenAuthenticationFilter`, or `WebAuthnAuthenticationFilter`, call `setRequiredAuthenticationRequestMatcher` instead to provide this `PathPatternRequestMatcher` in advance. |
|
|
|
That is, change this: |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
UsernamePasswordAuthenticationFilter usernamePassword = new UsernamePasswordAuthenticationFilter(authenticationManager); |
|
usernamePassword.setFilterProcessingUrl("/my/processing/url"); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
val usernamePassword = UsernamePasswordAuthenticationFilter(authenticationManager) |
|
usernamePassword.setFilterProcessingUrl("/my/processing/url") |
|
---- |
|
====== |
|
|
|
to this: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
UsernamePasswordAuthenticationFilter usernamePassword = new UsernamePasswordAuthenticationFilter(authenticationManager); |
|
RequestMatcher requestMatcher = PathPatternRequestMatcher.withDefaults().matcher("/my/processing/url"); |
|
usernamePassword.setRequest(requestMatcher); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
val usernamePassword = UsernamePasswordAuthenticationFilter(authenticationManager) |
|
val requestMatcher = PathPatternRequestMatcher.withDefaults().matcher("/my/processing/url") |
|
usernamePassword.setRequest(requestMatcher) |
|
---- |
|
====== |
|
|
|
[NOTE] |
|
----- |
|
Most applications use the DSL instead of setting the `filterProcessingUrl` directly on a filter instance. |
|
----- |
|
|
|
=== Migrate CAS Proxy Receptor Request Matcher |
|
|
|
Spring Security 6 converts any configured `proxyReceptorUrl` to a request matcher that matches the end of the request, that is `/**/proxy/receptor`. |
|
In Spring Security 7, this pattern is not allowed and will change to using `PathPatternRequestMatcher`. |
|
Also in Spring Security 7m the URL should by absolute, excluding any context path, like so: `/proxy/receptor`. |
|
|
|
So to prepare for these change, you can use `setProxyReceptorRequestMatcher` instead of `setProxyReceptorUrl`. |
|
|
|
That is, change this: |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
casAuthentication.setProxyReceptorUrl("/proxy/receptor"); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
casAuthentication.setProxyReceptorUrl("/proxy/receptor") |
|
---- |
|
====== |
|
|
|
to this: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
casAuthentication.setProxyReceptorUrl(PathPatternRequestMatcher.withDefaults().matcher("/proxy/receptor")); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
casAuthentication.setProxyReceptorUrl(PathPatternRequestMatcher.withDefaults().matcher("/proxy/receptor")) |
|
---- |
|
====== |
|
|
|
=== Migrate your WebInvocationPrivilegeEvaluator |
|
|
|
If you are using Spring Security's JSP Taglibs or are using `WebInvocationPrivilegeEvaluator` directly, be aware of the following changes: |
|
|
|
1. `RequestMatcherWebInvocationPrivilegeEvaluator` is deprecated in favor of `AuthorizationManagerWebInvocationPrivilegeEvaluator` |
|
2. `HandlerMappingIntrospectorRequestTransformer` is deprecated in favor of `PathPatternRequestTransformer` |
|
|
|
If you are not constructing these directly, you can opt-in to both changes in advance by publishing a `PathPatternRequestTransformer` like so: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
HttpServletRequestTransformer pathPatternRequestTransformer() { |
|
return new PathPatternRequestTransformer(); |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Bean |
|
fun pathPatternRequestTransformer(): HttpServletRequestTransformer { |
|
return PathPatternRequestTransformer() |
|
} |
|
---- |
|
|
|
Xml:: |
|
+ |
|
[source,xml,role="secondary"] |
|
---- |
|
<b:bean class="org.springframework.security.web.access.PathPatternRequestTransformer"/> |
|
---- |
|
====== |
|
|
|
Spring Security will take this as a signal to use the new implementations. |
|
|
|
[[NOTE]] |
|
---- |
|
One difference you may notice is that `AuthorizationManagerWebPrivilegeInvocationEvaluator` allows the authentication to be `null` if the authorization rule is `permitAll`. |
|
|
|
Test your endpoints that `permitAll` in case JSP requests using this same require should not, in fact, be permitted. |
|
---- |
|
|
|
== Include the Servlet Path Prefix in Authorization Rules |
|
|
|
For many applications <<use-path-pattern, the above>> will make no difference since most commonly all URIs listed are matched by the default servlet. |
|
|
|
However, if you have other servlets with servlet path prefixes, xref:servlet/authorization/authorize-http-requests.adoc[then these paths now need to be supplied separately]. |
|
|
|
For example, if I have a Spring MVC controller with `@RequestMapping("/orders")` and my MVC application is deployed to `/mvc` (instead of the default servlet), then the URI for this endpoint is `/mvc/orders`. |
|
Historically, the Java DSL hasn't had a simple way to specify the servlet path prefix and Spring Security attempted to infer it. |
|
|
|
Over time, we learned that these inference would surprise developers. |
|
Instead of taking this responsibility away from developers, now it is simpler to specify the servlet path prefix like so: |
|
|
|
[method,java] |
|
---- |
|
PathPatternRequestParser.Builder servlet = PathPatternRequestParser.withDefaults().basePath("/mvc"); |
|
http |
|
.authorizeHttpRequests((authorize) -> authorize |
|
.requestMatchers(servlet.pattern("/orders/**").matcher()).authenticated() |
|
) |
|
---- |
|
|
|
|
|
For paths that belong to the default servlet, use `PathPatternRequestParser.withDefaults()` instead: |
|
|
|
[method,java] |
|
---- |
|
PathPatternRequestParser.Builder request = PathPatternRequestParser.withDefaults(); |
|
http |
|
.authorizeHttpRequests((authorize) -> authorize |
|
.requestMatchers(request.pattern("/js/**").matcher()).authenticated() |
|
) |
|
---- |
|
|
|
Note that this doesn't address every kind of servlet since not all servlets have a path prefix. |
|
For example, expressions that match the JSP Servlet might use an ant pattern `/**/*.jsp`. |
|
|
|
There is not yet a general-purpose replacement for these, and so you are encouraged to use `RegexRequestMatcher`, like so: `regexMatcher("\\.jsp$")`. |
|
|
|
For many applications this will make no difference since most commonly all URIs listed are matched by the default servlet. |
|
|
|
[[use-redirect-to-https]] |
|
== Use RedirectToHttps Instead of Channel Security |
|
|
|
Years ago, HTTPS at large was enough of a performance and configuration concern that applications wanted to be able to decide which segments of an application would require HTTPS. |
|
|
|
`requires-channel` in XML and `requiresChannel` in Java Config allowed configurating an application with that in mind: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
http |
|
.requiresChannel((channel) -> channel |
|
.requestMatchers("/secure/**").requiresSecureChannel() |
|
.requestMatchers("/insecure/**").requiresInsecureChannel() |
|
) |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
http { |
|
requiresChannel { |
|
secure("/secure/**") |
|
seccure("/insecure/**", "REQUIRES_INSECURE_CHANNEL") |
|
} |
|
} |
|
---- |
|
|
|
Xml:: |
|
+ |
|
[source,xml,role="secondary"] |
|
---- |
|
<http> |
|
<intercept-url pattern="/secure/**" access="authenticated" requires-channel="REQUIRES_SECURE_CHANNEL"/> |
|
<intercept-url pattern="/insecure/**" access="authenticated" requires-channel="REQUIRES_INSECURE_CHANNEL"/> |
|
</http> |
|
---- |
|
====== |
|
|
|
Modern applications should either always require HTTPS. |
|
However, there are times, like when developing locally, when one would like the application to use HTTP. |
|
Or, you may have continuing circumstances that require part of your application to be HTTP. |
|
|
|
In any case, you can migrate to `redirect-to-https-request-matcher-ref` and `redirectToHttps` by first constructing a `RequestMatcher` that contains all circumstances where redirecting to HTTPS is needed. |
|
Then you can reference that request matcher like so: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
http |
|
.redirectToHttps((https) -> https.requestMatchers("/secure/**")) |
|
// ... |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
var secure: RequestMatcher = PathPatternRequestMatcher.withDefaults().pattern("/secure/**") |
|
http { |
|
redirectToHttps { |
|
requestMatchers = secure |
|
} |
|
// ... |
|
} |
|
---- |
|
|
|
Xml:: |
|
+ |
|
[source,xml,role="secondary"] |
|
---- |
|
<b:bean id="builder" class="org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher$Builder"/> |
|
<b:bean id="secure" class="org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher" factory-bean="builder" factory-method="matcher"> |
|
<b:constructor-arg value="/secure/**"/> |
|
</b:bean> |
|
<http redirect-to-https-request-matcher-ref="secure"> |
|
<intercept-url pattern="/secure/**" access="authenticated"/> |
|
<intercept-url pattern="/insecure/**" access="authenticated"/> |
|
<!-- ... --> |
|
</http> |
|
---- |
|
====== |
|
|
|
[TIP] |
|
===== |
|
If you have several circumstances where HTTP is needed, consider using `OrRequestMatcher` to combine them into a single `RequestMatcher` instance. |
|
===== |
|
|
|
== Use `setCookieCustomizer` instead of individual setters |
|
|
|
In favor of a simpler API, `CookieCsrfTokenRepository#setCookieCustomizer` allows you to change any aspect of the cookie, replacing `setCookieHttpOnly`, `setCookieMaxAge`, `setSecure`, and `setCookieDomain`. |
|
|
|
Change this: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
CookeCsrfTokenRepository csrf = CookeCsrfTokenRepository.withHttpOnlyFalse(); |
|
csrf.setCookieMaxAge(86400) |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
val csrf = CookeCsrfTokenRepository.withHttpOnlyFalse() |
|
csrf.setCookieMaxAge(86400) |
|
---- |
|
====== |
|
|
|
to this: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
CookeCsrfTokenRepository csrf = CookeCsrfTokenRepository.withHttpOnlyFalse(); |
|
csrf.setCookieCustomizer((c) -> c.maxAge(86400)); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
val csrf = CookeCsrfTokenRepository.withHttpOnlyFalse() |
|
csrf.setCookieCustomizer { -> it.maxAge(86400) } |
|
---- |
|
======
|
|
|