16 changed files with 98 additions and 1636 deletions
@ -1,68 +0,0 @@ |
|||||||
= Authentication Changes |
|
||||||
|
|
||||||
== Opaque Token Credentials Will Be Encoded For You |
|
||||||
|
|
||||||
In order to comply more closely with the Introspection RFC, Spring Security's opaque token support will encode the client id and secret before creating the authorization header. |
|
||||||
This change means you will no longer have to encode the client id and secret yourself. |
|
||||||
|
|
||||||
If your client id or secret contain URL-unsafe characters, then you can prepare yourself for this change by doing the following: |
|
||||||
|
|
||||||
=== Replace Usage of `introspectionClientCredentials` |
|
||||||
|
|
||||||
Since Spring Security can now do the encoding for you, replace xref:servlet/oauth2/resource-server/opaque-token.adoc#oauth2resourceserver-opaque-introspectionuri-dsl[using `introspectionClientCredentials`] with publishing the following `@Bean`: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
OpaqueTokenIntrospector introspector() { |
|
||||||
return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) |
|
||||||
.clientId(unencodedClientId).clientSecret(unencodedClientSecret).build(); |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,kotlin,role="secondary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
fun introspector(): OpaqueTokenIntrospector { |
|
||||||
return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) |
|
||||||
.clientId(unencodedClientId).clientSecret(unencodedClientSecret).build() |
|
||||||
} |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
The above will be the default in 7.0. |
|
||||||
|
|
||||||
If this setting gives you trouble or you cannot apply it for now, you can use the `RestOperations` constructor instead: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
OpaqueTokenIntrospector introspector() { |
|
||||||
RestTemplate rest = new RestTemplate(); |
|
||||||
rest.addInterceptor(new BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret)); |
|
||||||
return new SpringOpaqueTokenIntrospector(introspectionUri, rest); |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,kotlin,role="secondary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
fun introspector(): OpaqueTokenIntrospector { |
|
||||||
val rest = RestTemplate() |
|
||||||
rest.addInterceptor(BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret)) |
|
||||||
return SpringOpaqueTokenIntrospector(introspectionUri, rest) |
|
||||||
} |
|
||||||
---- |
|
||||||
====== |
|
||||||
@ -1,24 +0,0 @@ |
|||||||
= Authorization Changes |
|
||||||
|
|
||||||
The following sections relate to how to adapt to changes in the authorization support. |
|
||||||
|
|
||||||
== Method Security |
|
||||||
|
|
||||||
[[compile-with-parameters]] |
|
||||||
=== Compile With `-parameters` |
|
||||||
|
|
||||||
Spring Framework 6.1 https://github.com/spring-projects/spring-framework/issues/29559[removes LocalVariableTableParameterNameDiscoverer]. |
|
||||||
This affects how `@PreAuthorize` and other xref:servlet/authorization/method-security.adoc[method security] annotations will process parameter names. |
|
||||||
If you are using method security annotations with parameter names, for example: |
|
||||||
|
|
||||||
[source,java] |
|
||||||
.Method security annotation using `id` parameter name |
|
||||||
---- |
|
||||||
@PreAuthorize("@authz.checkPermission(#id, authentication)") |
|
||||||
public void doSomething(Long id) { |
|
||||||
// ... |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
You must compile with `-parameters` to ensure that the parameter names are available at runtime. |
|
||||||
For more information about this, please visit the https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#core-container[Upgrading to Spring Framework 6.1 page]. |
|
||||||
@ -1,125 +0,0 @@ |
|||||||
= Configuration Migrations |
|
||||||
|
|
||||||
The following steps relate to changes around how to configure `HttpSecurity`, `WebSecurity` and related components. |
|
||||||
|
|
||||||
== Use the Lambda DSL |
|
||||||
|
|
||||||
The Lambda DSL is present in Spring Security since version 5.2, and it allows HTTP security to be configured using lambdas. |
|
||||||
|
|
||||||
You may have seen this style of configuration in the Spring Security documentation or samples. |
|
||||||
Let us take a look at how a lambda configuration of HTTP security compares to the previous configuration style. |
|
||||||
|
|
||||||
[source,java] |
|
||||||
.Configuration using lambdas |
|
||||||
---- |
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
public class SecurityConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
http |
|
||||||
.authorizeHttpRequests(authorize -> authorize |
|
||||||
.requestMatchers("/blog/**").permitAll() |
|
||||||
.anyRequest().authenticated() |
|
||||||
) |
|
||||||
.formLogin(formLogin -> formLogin |
|
||||||
.loginPage("/login") |
|
||||||
.permitAll() |
|
||||||
) |
|
||||||
.rememberMe(Customizer.withDefaults()); |
|
||||||
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
[source,java] |
|
||||||
.Equivalent configuration without using lambdas |
|
||||||
---- |
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
public class SecurityConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
http |
|
||||||
.authorizeHttpRequests() |
|
||||||
.requestMatchers("/blog/**").permitAll() |
|
||||||
.anyRequest().authenticated() |
|
||||||
.and() |
|
||||||
.formLogin() |
|
||||||
.loginPage("/login") |
|
||||||
.permitAll() |
|
||||||
.and() |
|
||||||
.rememberMe(); |
|
||||||
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
The Lambda DSL is the preferred way to configure Spring Security, the prior configuration style will not be valid in Spring Security 7 where the usage of the Lambda DSL will be required. |
|
||||||
This has been done mainly for a couple of reasons: |
|
||||||
|
|
||||||
- The previous way it was not clear what object was getting configured without knowing what the return type was. |
|
||||||
The deeper the nesting the more confusing it became. |
|
||||||
Even experienced users would think that their configuration was doing one thing when in fact, it was doing something else. |
|
||||||
|
|
||||||
- Consistency. |
|
||||||
Many code bases switched between the two styles which caused inconsistencies that made understanding the configuration difficult and often led to misconfigurations. |
|
||||||
|
|
||||||
=== Lambda DSL Configuration Tips |
|
||||||
|
|
||||||
When comparing the two samples above, you will notice some key differences: |
|
||||||
|
|
||||||
- In the Lambda DSL there is no need to chain configuration options using the `.and()` method. |
|
||||||
The `HttpSecurity` instance is automatically returned for further configuration after the call to the lambda method. |
|
||||||
|
|
||||||
- `Customizer.withDefaults()` enables a security feature using the defaults provided by Spring Security. |
|
||||||
This is a shortcut for the lambda expression `it -> {}`. |
|
||||||
|
|
||||||
=== WebFlux Security |
|
||||||
|
|
||||||
You may also configure WebFlux security using lambdas in a similar manner. |
|
||||||
Below is an example configuration using lambdas. |
|
||||||
|
|
||||||
[source,java] |
|
||||||
.WebFlux configuration using lambdas |
|
||||||
---- |
|
||||||
@Configuration |
|
||||||
@EnableWebFluxSecurity |
|
||||||
public class SecurityConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { |
|
||||||
http |
|
||||||
.authorizeExchange(exchanges -> exchanges |
|
||||||
.pathMatchers("/blog/**").permitAll() |
|
||||||
.anyExchange().authenticated() |
|
||||||
) |
|
||||||
.httpBasic(Customizer.withDefaults()) |
|
||||||
.formLogin(formLogin -> formLogin |
|
||||||
.loginPage("/login") |
|
||||||
); |
|
||||||
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
=== Goals of the Lambda DSL |
|
||||||
|
|
||||||
The Lambda DSL was created to accomplish to following goals: |
|
||||||
|
|
||||||
- Automatic indentation makes the configuration more readable. |
|
||||||
- There is no need to chain configuration options using `.and()` |
|
||||||
- The Spring Security DSL has a similar configuration style to other Spring DSLs such as Spring Integration and Spring Cloud Gateway. |
|
||||||
|
|
||||||
== Use `.with()` instead of `.apply()` for Custom DSLs |
|
||||||
|
|
||||||
In versions prior to 6.2, if you had a xref:servlet/configuration/java.adoc#jc-custom-dsls[custom DSL], you would apply it to the `HttpSecurity` using the `HttpSecurity#apply(...)` method. |
|
||||||
However, starting from version 6.2, this method is deprecated and will be removed in 7.0 because it will no longer be possible to chain configurations using `.and()` once `.and()` is removed (see https://github.com/spring-projects/spring-security/issues/13067). |
|
||||||
Instead, it is recommended to use the new `.with(...)` method. |
|
||||||
For more information about how to use `.with(...)` please refer to the xref:servlet/configuration/java.adoc#jc-custom-dsls[Custom DSLs section]. |
|
||||||
@ -1,11 +0,0 @@ |
|||||||
= LDAP Migrations |
|
||||||
|
|
||||||
The following steps relate to changes around how to configure the LDAP components and how to use an embedded LDAP server. |
|
||||||
|
|
||||||
== Use `UnboundId` instead of `ApacheDS` |
|
||||||
|
|
||||||
ApacheDS has not had a GA release for a considerable period, and its classes in Spring Security were https://github.com/spring-projects/spring-security/pull/6376[deprecated in version 5.2]. |
|
||||||
Consequently, support for ApacheDS will be discontinued in version 7.0. |
|
||||||
|
|
||||||
If you are currently using ApacheDS as an embedded LDAP server, we recommend migrating to https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundId]. |
|
||||||
You can find instructions in xref:servlet/authentication/passwords/ldap.adoc#servlet-authentication-ldap-embedded[this section] that describe how to set up an embedded UnboundId LDAP server. |
|
||||||
@ -1,172 +0,0 @@ |
|||||||
= OAuth 2.0 Changes |
|
||||||
|
|
||||||
== Validate `typ` Header with `JwtTypeValidator` |
|
||||||
|
|
||||||
`NimbusJwtDecoder` in Spring Security 7 will move `typ` header validation to `JwtTypeValidator` instead of relying on Nimbus. |
|
||||||
This brings it in line with `NimbusJwtDecoder` validating claims instead of relying on Nimbus to validate them. |
|
||||||
|
|
||||||
If you are changing Nimbus's default type validation in a `jwtProcessorCustomizer` method, then you should move that to `JwtTypeValidator` or an implementation of `OAuth2TokenValidator` of your own. |
|
||||||
|
|
||||||
To check if you are prepared for this change, add the default `JwtTypeValidator` to your list of validators, as this will be included by default in 7: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
JwtDecoder jwtDecoder() { |
|
||||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
|
||||||
.validateTypes(false) <1> |
|
||||||
// ... your remaining configuration |
|
||||||
.build(); |
|
||||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( |
|
||||||
new JwtIssuerValidator(location), JwtTypeValidator.jwt())); <2> |
|
||||||
return jwtDecoder; |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,kotlin,role="secondary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
fun jwtDecoder(): JwtDecoder { |
|
||||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
|
||||||
.validateTypes(false) <1> |
|
||||||
// ... your remaining configuration |
|
||||||
.build() |
|
||||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( |
|
||||||
JwtIssuerValidator(location), JwtTypeValidator.jwt())) <2> |
|
||||||
return jwtDecoder |
|
||||||
} |
|
||||||
---- |
|
||||||
====== |
|
||||||
<1> - Switch off Nimbus verifying the `typ` (this will be off by default in 7) |
|
||||||
<2> - Add the default `typ` validator (this will be included by default in 7) |
|
||||||
|
|
||||||
Note the default value verifies that the `typ` value either be `JWT` or not present, which is the same as the Nimbus default. |
|
||||||
It is also aligned with https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.9[RFC 7515] which states that `typ` is optional. |
|
||||||
|
|
||||||
|
|
||||||
=== I'm Using A `DefaultJOSEObjectTypeVerifier` |
|
||||||
|
|
||||||
If you have something like the following in your configuration: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
JwtDecoder jwtDecoder() { |
|
||||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
|
||||||
.jwtProcessorCustomizer((c) -> c |
|
||||||
.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>("JOSE")) |
|
||||||
) |
|
||||||
.build(); |
|
||||||
return jwtDecoder; |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,kotlin,role="secondary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
fun jwtDecoder(): JwtDecoder { |
|
||||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
|
||||||
.jwtProcessorCustomizer { |
|
||||||
it.setJWSTypeVerifier(DefaultJOSEObjectTypeVerifier("JOSE")) |
|
||||||
} |
|
||||||
.build() |
|
||||||
return jwtDecoder |
|
||||||
} |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
Then change this to: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
JwtDecoder jwtDecoder() { |
|
||||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
|
||||||
.validateTypes(false) |
|
||||||
.build(); |
|
||||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( |
|
||||||
new JwtIssuerValidator(location), new JwtTypeValidator("JOSE"))); |
|
||||||
return jwtDecoder; |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,kotlin,role="secondary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
fun jwtDecoder(): JwtDecoder { |
|
||||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
|
||||||
.validateTypes(false) |
|
||||||
.build() |
|
||||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( |
|
||||||
JwtIssuerValidator(location), JwtTypeValidator("JOSE"))) |
|
||||||
return jwtDecoder |
|
||||||
} |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
To indicate that the `typ` header is optional, use `#setAllowEmpty(true)` (this is the equivalent of including `null` in the list of allowed types in `DefaultJOSEObjectTypeVerifier`). |
|
||||||
|
|
||||||
=== I want to opt-out |
|
||||||
|
|
||||||
If you want to keep doing things the way that you are, then the steps are similar, just in reverse: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
JwtDecoder jwtDecoder() { |
|
||||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
|
||||||
.validateTypes(true) <1> |
|
||||||
.jwtProcessorCustomizer((c) -> c |
|
||||||
.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>("JOSE")) |
|
||||||
) |
|
||||||
.build(); |
|
||||||
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>( |
|
||||||
new JwtTimestampValidator(), new JwtIssuerValidator(location))); <2> |
|
||||||
return jwtDecoder; |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,kotlin,role="secondary"] |
|
||||||
---- |
|
||||||
@Bean |
|
||||||
fun jwtDecoder(): JwtDecoder { |
|
||||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
|
||||||
.validateTypes(true) <1> |
|
||||||
.jwtProcessorCustomizer { |
|
||||||
it.setJWSTypeVerifier(DefaultJOSEObjectTypeVerifier("JOSE")) |
|
||||||
} |
|
||||||
.build() |
|
||||||
jwtDecoder.setJwtValidator(DelegatingOAuth2TokenValidator( |
|
||||||
JwtTimestampValidator(), JwtIssuerValidator(location))) <2> |
|
||||||
return jwtDecoder |
|
||||||
} |
|
||||||
---- |
|
||||||
====== |
|
||||||
<1> - leave Nimbus type verification on |
|
||||||
<2> - specify the list of validators you need, excluding `JwtTypeValidator` |
|
||||||
|
|
||||||
For additional guidance, please see the xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-validation[JwtDecoder Validators] section in the reference. |
|
||||||
@ -1,165 +0,0 @@ |
|||||||
= Saml 2.0 Migrations |
|
||||||
|
|
||||||
== Continue Filter Chain When No Relying Party Found |
|
||||||
|
|
||||||
In Spring Security 6, `Saml2WebSsoAuthenticationFilter` throws an exception when the request URI matches, but no relying party registration is found. |
|
||||||
|
|
||||||
There are a number of cases when an application would not consider this an error situation. |
|
||||||
For example, this filter doesn't know how the `AuthorizationFilter` will respond to a missing relying party. |
|
||||||
In some cases it may be allowable. |
|
||||||
|
|
||||||
In other cases, you may want your `AuthenticationEntryPoint` to be invoked, which would happen if this filter were to allow the request to continue to the `AuthorizationFilter`. |
|
||||||
|
|
||||||
To improve this filter's flexibility, in Spring Security 7 it will continue the filter chain when there is no relying party registration found instead of throwing an exception. |
|
||||||
|
|
||||||
For many applications, the only notable change will be that your `authenticationEntryPoint` will be invoked if the relying party registration cannot be found. |
|
||||||
When you have only one asserting party, this means by default a new authentication request will be built and sent back to the asserting party, which may cause a "Too Many Redirects" loop. |
|
||||||
|
|
||||||
To see if you are affected in this way, you can prepare for this change in 6 by setting the following property in `Saml2WebSsoAuthenticationFilter`: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
http |
|
||||||
.saml2Login((saml2) -> saml2 |
|
||||||
.withObjectPostProcessor(new ObjectPostProcessor<Saml2WebSsoAuhenticaionFilter>() { |
|
||||||
@Override |
|
||||||
public Saml2WebSsoAuthenticationFilter postProcess(Saml2WebSsoAuthenticationFilter filter) { |
|
||||||
filter.setContinueChainWhenNoRelyingPartyRegistrationFound(true); |
|
||||||
return filter; |
|
||||||
} |
|
||||||
}) |
|
||||||
) |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,kotlin,role="secondary"] |
|
||||||
---- |
|
||||||
http { |
|
||||||
saml2Login { } |
|
||||||
withObjectPostProcessor( |
|
||||||
object : ObjectPostProcessor<Saml2WebSsoAuhenticaionFilter?>() { |
|
||||||
override fun postProcess(filter: Saml2WebSsoAuthenticationFilter): Saml2WebSsoAuthenticationFilter { |
|
||||||
filter.setContinueChainWhenNoRelyingPartyRegistrationFound(true) |
|
||||||
return filter |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
Xml:: |
|
||||||
+ |
|
||||||
[source,xml,role="secondary"] |
|
||||||
---- |
|
||||||
<b:bean id="saml2PostProcessor" class="org.example.MySaml2WebSsoAuthenticationFilterBeanPostProcessor"/> |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
== Validate Response After Validating Assertions |
|
||||||
|
|
||||||
In Spring Security 6, the order of authenticating a `<saml2:Response>` is as follows: |
|
||||||
|
|
||||||
1. Verify the Response Signature, if any |
|
||||||
2. Decrypt the Response |
|
||||||
3. Validate Response attributes, like Destination and Issuer |
|
||||||
4. For each assertion, verify the signature, decrypt, and then validate its fields |
|
||||||
5. Check to ensure that the response has at least one assertion with a name field |
|
||||||
|
|
||||||
This ordering sometimes poses challenges since some response validation is being done in Step 3 and some in Step 5. |
|
||||||
Specifically, this poses a chellenge when an application doesn't have a name field and doesn't need it to be validated. |
|
||||||
|
|
||||||
In Spring Security 7, this is simplified by moving response validation to after assertion validation and combining the two separate validation steps 3 and 5. |
|
||||||
When this is complete, response validation will no longer check for the existence of the `NameID` attribute and rely on ``ResponseAuthenticationConverter``s to do this. |
|
||||||
|
|
||||||
This will add support ``ResponseAuthenticationConverter``s that don't use the `NameID` element in their `Authentication` instance and so don't need it validated. |
|
||||||
|
|
||||||
To opt-in to this behavior in advance, use `OpenSaml5AuthenticationProvider#setValidateResponseAfterAssertions` to `true` like so: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider(); |
|
||||||
provider.setValidateResponseAfterAssertions(true); |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,kotlin,role="secondary"] |
|
||||||
---- |
|
||||||
val provider = OpenSaml5AuthenticationProvider() |
|
||||||
provider.setValidateResponseAfterAssertions(true) |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
This will change the authentication steps as follows: |
|
||||||
|
|
||||||
1. Verify the Response Signature, if any |
|
||||||
2. Decrypt the Response |
|
||||||
3. For each assertion, verify the signature, decrypt, and then validate its fields |
|
||||||
4. Validate Response attributes, like Destination and Issuer |
|
||||||
|
|
||||||
Note that if you have a custom response authentication converter, then you are now responsible to check if the `NameID` element exists in the event that you need it. |
|
||||||
|
|
||||||
Alternatively to updating your response authentication converter, you can specify a custom `ResponseValidator` that adds back in the check for the `NameID` element as follows: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider(); |
|
||||||
provider.setValidateResponseAfterAssertions(true); |
|
||||||
ResponseValidator responseValidator = ResponseValidator.withDefaults((responseToken) -> { |
|
||||||
Response response = responseToken.getResponse(); |
|
||||||
Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); |
|
||||||
Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, |
|
||||||
"Assertion [" + firstAssertion.getID() + "] is missing a subject"); |
|
||||||
Saml2ResponseValidationResult failed = Saml2ResponseValidationResult.failure(error); |
|
||||||
if (assertion.getSubject() == null) { |
|
||||||
return failed; |
|
||||||
} |
|
||||||
if (assertion.getSubject().getNameID() == null) { |
|
||||||
return failed; |
|
||||||
} |
|
||||||
if (assertion.getSubject().getNameID().getValue() == null) { |
|
||||||
return failed; |
|
||||||
} |
|
||||||
return Saml2ResponseValidationResult.success(); |
|
||||||
}); |
|
||||||
provider.setResponseValidator(responseValidator); |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,kotlin,role="secondary"] |
|
||||||
---- |
|
||||||
val provider = OpenSaml5AuthenticationProvider() |
|
||||||
provider.setValidateResponseAfterAssertions(true) |
|
||||||
val responseValidator = ResponseValidator.withDefaults { responseToken: ResponseToken -> |
|
||||||
val response = responseToken.getResponse() |
|
||||||
val assertion = CollectionUtils.firstElement(response.getAssertions()) |
|
||||||
val error = Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, |
|
||||||
"Assertion [" + firstAssertion.getID() + "] is missing a subject") |
|
||||||
val failed = Saml2ResponseValidationResult.failure(error) |
|
||||||
if (assertion.getSubject() == null) { |
|
||||||
return@withDefaults failed |
|
||||||
} |
|
||||||
if (assertion.getSubject().getNameID() == null) { |
|
||||||
return@withDefaults failed |
|
||||||
} |
|
||||||
if (assertion.getSubject().getNameID().getValue() == null) { |
|
||||||
return@withDefaults failed |
|
||||||
} |
|
||||||
return@withDefaults Saml2ResponseValidationResult.success() |
|
||||||
} |
|
||||||
provider.setResponseValidator(responseValidator) |
|
||||||
---- |
|
||||||
====== |
|
||||||
@ -1,523 +0,0 @@ |
|||||||
= 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. |
|
||||||
===== |
|
||||||
@ -1,9 +1,9 @@ |
|||||||
[[preparing]] |
[[preparing]] |
||||||
= Preparing for 7.0 |
= Preparing for 8.0 |
||||||
:page-section-summary-toc: 1 |
:page-section-summary-toc: 1 |
||||||
|
|
||||||
While Spring Security 7.0 does not have a release date yet, it is important to start preparing for it now. |
While Spring Security 8.0 does not have a release date yet, it is important to start preparing for it now. |
||||||
|
|
||||||
This preparation guide is designed to summarize the biggest changes in Spring Security 7.0 and provide steps to prepare for them. |
This preparation guide is designed to summarize the biggest changes in Spring Security 8.0 and provide steps to prepare for them. |
||||||
|
|
||||||
It is important to keep your application up to date with the latest Spring Security 6 and Spring Boot 3 releases. |
It is important to keep your application up to date with the latest Spring Security 7 and Spring Boot 4 releases. |
||||||
@ -1,187 +0,0 @@ |
|||||||
= Authentication Migrations |
|
||||||
|
|
||||||
The following steps relate to how to finish migrating authentication support. |
|
||||||
|
|
||||||
== 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/web/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. |
|
||||||
|
|
||||||
So, if you opted into this behavior by setting `rethrowAuthenticationServiceException` to `true`, you can now remove it like so: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
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 = AuthenticationFilter(...) |
|
||||||
val handler: AuthenticationEntryPointFailureHandler = AuthenticationEntryPointFailureHandler(...) |
|
||||||
handler.setRethrowAuthenticationServiceException(true) |
|
||||||
authenticationFilter.setAuthenticationFailureHandler(handler) |
|
||||||
---- |
|
||||||
|
|
||||||
Xml:: |
|
||||||
+ |
|
||||||
[source,xml,role="secondary"] |
|
||||||
---- |
|
||||||
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.AuthenticationFilter"> |
|
||||||
<!-- ... --> |
|
||||||
<property ref="authenticationFailureHandler"/> |
|
||||||
</bean> |
|
||||||
|
|
||||||
<bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler"> |
|
||||||
<property name="rethrowAuthenticationServiceException" value="true"/> |
|
||||||
</bean> |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
changes to: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
AuthenticationFilter authenticationFilter = new AuthenticationFilter(...); |
|
||||||
AuthenticationEntryPointFailureHandler handler = new AuthenticationEntryPointFailureHandler(...); |
|
||||||
authenticationFilter.setAuthenticationFailureHandler(handler); |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,kotlin,role="secondary"] |
|
||||||
---- |
|
||||||
val authenticationFilter: AuthenticationFilter = AuthenticationFilter(...) |
|
||||||
val handler: AuthenticationEntryPointFailureHandler = AuthenticationEntryPointFailureHandler(...) |
|
||||||
authenticationFilter.setAuthenticationFailureHandler(handler) |
|
||||||
---- |
|
||||||
|
|
||||||
Xml:: |
|
||||||
+ |
|
||||||
[source,xml,role="secondary"] |
|
||||||
---- |
|
||||||
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.AuthenticationFilter"> |
|
||||||
<!-- ... --> |
|
||||||
<property ref="authenticationFailureHandler"/> |
|
||||||
</bean> |
|
||||||
|
|
||||||
<bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler"> |
|
||||||
<!-- ... --> |
|
||||||
</bean> |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
[[servlet-opt-in-sha256-rememberme]] |
|
||||||
== Use SHA-256 in Remember Me |
|
||||||
|
|
||||||
In 6.0, the `TokenBasedRememberMeServices` uses SHA-256 to encode and match the token. |
|
||||||
To complete the migration, any default values can be removed. |
|
||||||
|
|
||||||
For example, if you opted in to the 6.0 default for `encodingAlgorithm` and `matchingAlgorithm` like so: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
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"] |
|
||||||
---- |
|
||||||
<http> |
|
||||||
<remember-me services-ref="rememberMeServices"/> |
|
||||||
</http> |
|
||||||
<bean id="rememberMeServices" class= |
|
||||||
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices"> |
|
||||||
<property name="userDetailsService" ref="myUserDetailsService"/> |
|
||||||
<property name="key" value="springRocks"/> |
|
||||||
<property name="matchingAlgorithm" value="SHA256"/> |
|
||||||
<property name="encodingAlgorithm" value="SHA256"/> |
|
||||||
</bean> |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
then the defaults can be removed: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
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) { |
|
||||||
return new TokenBasedRememberMeServices(myKey, userDetailsService); |
|
||||||
} |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
XML:: |
|
||||||
+ |
|
||||||
[source,xml,role="secondary"] |
|
||||||
---- |
|
||||||
<http> |
|
||||||
<remember-me services-ref="rememberMeServices"/> |
|
||||||
</http> |
|
||||||
<bean id="rememberMeServices" class= |
|
||||||
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices"> |
|
||||||
<property name="userDetailsService" ref="myUserDetailsService"/> |
|
||||||
<property name="key" value="springRocks"/> |
|
||||||
</bean> |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
== 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`. |
|
||||||
|
|
||||||
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`. |
|
||||||
If you configured the `GrantedAuthoritiesMapper` only for the purpose of updating to 6.0, you can remove it completely. |
|
||||||
@ -1,136 +0,0 @@ |
|||||||
= Authorization Migrations |
|
||||||
|
|
||||||
The following steps relate to how to finish migrating authorization support. |
|
||||||
|
|
||||||
== Use `AuthorizationManager` for Method Security |
|
||||||
|
|
||||||
There are no further migration steps for this feature. |
|
||||||
|
|
||||||
== Use `AuthorizationManager` for Message Security |
|
||||||
|
|
||||||
In 6.0, `<websocket-message-broker>` defaults `use-authorization-manager` to `true`. |
|
||||||
So, to complete migration, remove any `websocket-message-broker@use-authorization-manager=true` attribute. |
|
||||||
|
|
||||||
For example: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Xml:: |
|
||||||
+ |
|
||||||
[source,xml,role="primary"] |
|
||||||
---- |
|
||||||
<websocket-message-broker use-authorization-manager="true"/> |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
changes to: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Xml:: |
|
||||||
+ |
|
||||||
[source,xml,role="primary"] |
|
||||||
---- |
|
||||||
<websocket-message-broker/> |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
There are no further migrations steps for Java or Kotlin for this feature. |
|
||||||
|
|
||||||
== Use `AuthorizationManager` for Request Security |
|
||||||
|
|
||||||
In 6.0, `<http>` defaults `once-per-request` to `false`, `filter-all-dispatcher-types` to `true`, and `use-authorization-manager` to `true`. |
|
||||||
Also, xref:servlet/authorization/authorize-http-requests.adoc[`authorizeHttpRequests#filterAllDispatcherTypes`] defaults to `true`. |
|
||||||
So, to complete migration, any defaults values can be removed. |
|
||||||
|
|
||||||
For example, if you opted in to the 6.0 default for `filter-all-dispatcher-types` or `authorizeHttpRequests#filterAllDispatcherTypes` like so: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
http |
|
||||||
.authorizeHttpRequests((authorize) -> authorize |
|
||||||
.filterAllDispatcherTypes(true) |
|
||||||
// ... |
|
||||||
) |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,java,role="secondary"] |
|
||||||
---- |
|
||||||
http { |
|
||||||
authorizeHttpRequests { |
|
||||||
filterAllDispatcherTypes = true |
|
||||||
// ... |
|
||||||
} |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
Xml:: |
|
||||||
+ |
|
||||||
[source,xml,role="secondary"] |
|
||||||
---- |
|
||||||
<http use-authorization-manager="true" filter-all-dispatcher-types="true"/> |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
then the defaults may be removed: |
|
||||||
|
|
||||||
[tabs] |
|
||||||
====== |
|
||||||
Java:: |
|
||||||
+ |
|
||||||
[source,java,role="primary"] |
|
||||||
---- |
|
||||||
http |
|
||||||
.authorizeHttpRequests((authorize) -> authorize |
|
||||||
// ... |
|
||||||
) |
|
||||||
---- |
|
||||||
|
|
||||||
Kotlin:: |
|
||||||
+ |
|
||||||
[source,java,role="secondary"] |
|
||||||
---- |
|
||||||
http { |
|
||||||
authorizeHttpRequests { |
|
||||||
// ... |
|
||||||
} |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
Xml:: |
|
||||||
+ |
|
||||||
[source,xml,role="secondary"] |
|
||||||
---- |
|
||||||
<http/> |
|
||||||
---- |
|
||||||
====== |
|
||||||
|
|
||||||
[NOTE] |
|
||||||
==== |
|
||||||
`once-per-request` applies only when `use-authorization-manager="false"` and `filter-all-dispatcher-types` only applies when `use-authorization-manager="true"` |
|
||||||
==== |
|
||||||
|
|
||||||
[[compile-with-parameters]] |
|
||||||
=== Compile With `-parameters` |
|
||||||
|
|
||||||
Spring Framework 6.1 https://github.com/spring-projects/spring-framework/issues/29559[removes LocalVariableTableParameterNameDiscoverer]. |
|
||||||
This affects how `@PreAuthorize` and other xref:servlet/authorization/method-security.adoc[method security] annotations will process parameter names. |
|
||||||
If you are using method security annotations with parameter names, for example: |
|
||||||
|
|
||||||
[source,java] |
|
||||||
.Method security annotation using `id` parameter name |
|
||||||
---- |
|
||||||
@PreAuthorize("@authz.checkPermission(#id, authentication)") |
|
||||||
public void doSomething(Long id) { |
|
||||||
// ... |
|
||||||
} |
|
||||||
---- |
|
||||||
|
|
||||||
You must compile with `-parameters` to ensure that the parameter names are available at runtime. |
|
||||||
For more information about this, please visit the https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#core-container[Upgrading to Spring Framework 6.1 page]. |
|
||||||
@ -1,44 +0,0 @@ |
|||||||
= Exploit Protection Migrations |
|
||||||
:spring-security-reference-base-url: https://docs.spring.io/spring-security/reference |
|
||||||
|
|
||||||
The 5.8 migration guide contains several steps for |
|
||||||
ifdef::spring-security-version[] |
|
||||||
{spring-security-reference-base-url}/5.8/migration/servlet/exploits.html[exploit protection migrations] when updating to 6.0. |
|
||||||
endif::[] |
|
||||||
ifndef::spring-security-version[] |
|
||||||
exploit protection migrations when updating to 6.0. |
|
||||||
endif::[] |
|
||||||
You are encouraged to follow those steps first. |
|
||||||
|
|
||||||
The following steps relate to how to finish migrating exploit protection support. |
|
||||||
|
|
||||||
== Defer Loading CsrfToken |
|
||||||
|
|
||||||
In Spring Security 5.8, the default `CsrfTokenRequestHandler` for making the `CsrfToken` available to the application is `CsrfTokenRequestAttributeHandler`. |
|
||||||
The default for the field `csrfRequestAttributeName` is `null`, which causes the CSRF token to be loaded on every request. |
|
||||||
|
|
||||||
In Spring Security 6, `csrfRequestAttributeName` defaults to `_csrf`. |
|
||||||
If you configured the following only for the purpose of updating to 6.0, you can now remove it: |
|
||||||
|
|
||||||
requestHandler.setCsrfRequestAttributeName("_csrf"); |
|
||||||
|
|
||||||
== Protect against CSRF BREACH |
|
||||||
|
|
||||||
In Spring Security 5.8, the default `CsrfTokenRequestHandler` for making the `CsrfToken` available to the application is `CsrfTokenRequestAttributeHandler`. |
|
||||||
`XorCsrfTokenRequestAttributeHandler` was added to allow opting into CSRF BREACH support. |
|
||||||
|
|
||||||
In Spring Security 6, `XorCsrfTokenRequestAttributeHandler` is the default `CsrfTokenRequestHandler` for making the `CsrfToken` available. |
|
||||||
If you configured the `XorCsrfTokenRequestAttributeHandler` only for the purpose of updating to 6.0, you can remove it completely. |
|
||||||
|
|
||||||
[NOTE] |
|
||||||
==== |
|
||||||
If you have set the `csrfRequestAttributeName` to `null` in order to opt out of deferred tokens, or if you have configured a `CsrfTokenRequestHandler` for any other reason, you can leave the configuration in place. |
|
||||||
==== |
|
||||||
|
|
||||||
== CSRF BREACH with WebSocket support |
|
||||||
|
|
||||||
In Spring Security 5.8, the default `ChannelInterceptor` for making the `CsrfToken` available with xref:servlet/integrations/websocket.adoc[WebSocket Security] is `CsrfChannelInterceptor`. |
|
||||||
`XorCsrfChannelInterceptor` was added to allow opting into CSRF BREACH support. |
|
||||||
|
|
||||||
In Spring Security 6, `XorCsrfChannelInterceptor` is the default `ChannelInterceptor` for making the `CsrfToken` available. |
|
||||||
If you configured the `XorCsrfChannelInterceptor` only for the purpose of updating to 6.0, you can remove it completely. |
|
||||||
@ -0,0 +1,80 @@ |
|||||||
|
= OAuth 2.0 Migrations |
||||||
|
|
||||||
|
== Validate `typ` Header with `JwtTypeValidator` |
||||||
|
|
||||||
|
If when following the 6.5 preparatory steps you set `validateTypes` to `false`, you can now remove it. |
||||||
|
You can also remove explicitly adding `JwtTypeValidator` to the list of defaults. |
||||||
|
|
||||||
|
For example, change this: |
||||||
|
|
||||||
|
[tabs] |
||||||
|
====== |
||||||
|
Java:: |
||||||
|
+ |
||||||
|
[source,java,role="primary"] |
||||||
|
---- |
||||||
|
@Bean |
||||||
|
JwtDecoder jwtDecoder() { |
||||||
|
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
||||||
|
.validateTypes(false) <1> |
||||||
|
// ... your remaining configuration |
||||||
|
.build(); |
||||||
|
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( |
||||||
|
new JwtIssuerValidator(location), JwtTypeValidator.jwt())); <2> |
||||||
|
return jwtDecoder; |
||||||
|
} |
||||||
|
---- |
||||||
|
|
||||||
|
Kotlin:: |
||||||
|
+ |
||||||
|
[source,kotlin,role="secondary"] |
||||||
|
---- |
||||||
|
@Bean |
||||||
|
fun jwtDecoder(): JwtDecoder { |
||||||
|
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
||||||
|
.validateTypes(false) <1> |
||||||
|
// ... your remaining configuration |
||||||
|
.build() |
||||||
|
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( |
||||||
|
JwtIssuerValidator(location), JwtTypeValidator.jwt())) <2> |
||||||
|
return jwtDecoder |
||||||
|
} |
||||||
|
---- |
||||||
|
====== |
||||||
|
<1> - Switch off Nimbus verifying the `typ` |
||||||
|
<2> - Add the default `typ` validator |
||||||
|
|
||||||
|
to this: |
||||||
|
|
||||||
|
[tabs] |
||||||
|
====== |
||||||
|
Java:: |
||||||
|
+ |
||||||
|
[source,java,role="primary"] |
||||||
|
---- |
||||||
|
@Bean |
||||||
|
JwtDecoder jwtDecoder() { |
||||||
|
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
||||||
|
// ... your remaining configuration <1> |
||||||
|
.build(); |
||||||
|
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(location)); <2> |
||||||
|
return jwtDecoder; |
||||||
|
} |
||||||
|
---- |
||||||
|
|
||||||
|
Kotlin:: |
||||||
|
+ |
||||||
|
[source,kotlin,role="secondary"] |
||||||
|
---- |
||||||
|
@Bean |
||||||
|
fun jwtDecoder(): JwtDecoder { |
||||||
|
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) |
||||||
|
// ... your remaining configuration |
||||||
|
.build() |
||||||
|
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(location)) <2> |
||||||
|
return jwtDecoder |
||||||
|
} |
||||||
|
---- |
||||||
|
====== |
||||||
|
<1> - `validateTypes` now defaults to `false` |
||||||
|
<2> - `JwtTypeValidator#jwt` is added by all `createDefaultXXX` methods |
||||||
@ -1,49 +0,0 @@ |
|||||||
= Session Management Migrations |
|
||||||
|
|
||||||
The following steps relate to how to finish migrating session management support. |
|
||||||
|
|
||||||
== Require Explicit Saving of 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. |
|
||||||
|
|
||||||
[NOTE] |
|
||||||
==== |
|
||||||
Saving the context is also needed when clearing it out, for example during logout. Refer to this section to xref:servlet/authentication/session-management.adoc#properly-clearing-authentication[know more about that]. |
|
||||||
==== |
|
||||||
|
|
||||||
If you are explicitly opting into Spring Security 6's new defaults, the following configuration can be removed to accept the Spring Security 6 defaults. |
|
||||||
|
|
||||||
|
|
||||||
include::partial$servlet/architecture/security-context-explicit.adoc[] |
|
||||||
|
|
||||||
== Multiple SecurityContextRepository |
|
||||||
|
|
||||||
In Spring Security 5, the default xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] was `HttpSessionSecurityContextRepository`. |
|
||||||
|
|
||||||
In Spring Security 6, the default `SecurityContextRepository` is `DelegatingSecurityContextRepository`. |
|
||||||
If you configured the `SecurityContextRepository` only for the purpose of updating to 6.0, you can remove it completely. |
|
||||||
|
|
||||||
== Deprecation in SecurityContextRepository |
|
||||||
|
|
||||||
There are no further migration steps for this deprecation. |
|
||||||
|
|
||||||
[[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[] |
|
||||||
Loading…
Reference in new issue