Revert unnecessary merges on 6.1.x
This commit removes unnecessary main-branch merges starting from9f8db22b77and adds the following needed commit(s) that were made afterward: -4d6ff49b9d-ed6ff670d1-c823b00794-44fad21363
This commit is contained in:
@@ -4,9 +4,13 @@
|
||||
* xref:whats-new.adoc[What's New]
|
||||
* xref:migration-7/index.adoc[Preparing for 7.0]
|
||||
** xref:migration-7/configuration.adoc[Configuration]
|
||||
** xref:migration-7/ldap.adoc[LDAP]
|
||||
* xref:migration/index.adoc[Migrating to 6.2]
|
||||
** xref:migration/authorization.adoc[Authorization Changes]
|
||||
* xref:migration/index.adoc[Migrating to 6.0]
|
||||
** xref:migration/servlet/index.adoc[Servlet Migrations]
|
||||
*** xref:migration/servlet/session-management.adoc[Session Management]
|
||||
*** xref:migration/servlet/exploits.adoc[Exploit Protection]
|
||||
*** xref:migration/servlet/authentication.adoc[Authentication]
|
||||
*** xref:migration/servlet/authorization.adoc[Authorization]
|
||||
** xref:migration/reactive.adoc[Reactive Migrations]
|
||||
* xref:getting-spring-security.adoc[Getting Spring Security]
|
||||
* xref:features/index.adoc[Features]
|
||||
** xref:features/authentication/index.adoc[Authentication]
|
||||
|
||||
@@ -20,13 +20,13 @@ Spring Security provides support for xref:features/exploits/headers.adoc#headers
|
||||
== Proxy Server Configuration
|
||||
|
||||
When using a proxy server, it is important to ensure that you have configured your application properly.
|
||||
For example, many applications have a load balancer that responds to request for https://example.com/ by forwarding the request to an application server at https://192.168.0.107
|
||||
Without proper configuration, the application server can not know that the load balancer exists and treats the request as though https://192.168.0.107:8080 was requested by the client.
|
||||
For example, many applications have a load balancer that responds to request for https://example.com/ by forwarding the request to an application server at https://192.168.1:8080
|
||||
Without proper configuration, the application server can not know that the load balancer exists and treats the request as though https://192.168.1:8080 was requested by the client.
|
||||
|
||||
To fix this, you can use https://tools.ietf.org/html/rfc7239[RFC 7239] to specify that a load balancer is being used.
|
||||
To make the application aware of this, you need to configure your application server to be aware of the X-Forwarded headers.
|
||||
For example, Tomcat uses https://tomcat.apache.org/tomcat-10.1-doc/api/org/apache/catalina/valves/RemoteIpValve.html[`RemoteIpValve`] and Jetty uses https://eclipse.dev/jetty/javadoc/jetty-11/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[`ForwardedRequestCustomizer`].
|
||||
Alternatively, Spring users can use https://docs.spring.io/spring-framework/reference/web/webmvc/filters.html#filters-forwarded-headers[`ForwardedHeaderFilter`] with the Servlet stack or https://docs.spring.io/spring-framework/reference/web/webflux/reactive-spring.html#webflux-forwarded-headers[`ForwardedHeaderTransformer`] with the Reactive stack.
|
||||
Alternatively, Spring users can use https://github.com/spring-projects/spring-framework/blob/v4.3.3.RELEASE/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java[`ForwardedHeaderFilter`].
|
||||
|
||||
Spring Boot users can use the `server.forward-headers-strategy` property to configure the application.
|
||||
See the https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.webserver.use-behind-a-proxy-server[Spring Boot documentation] for further details.
|
||||
Spring Boot users can use the `server.use-forward-headers` property to configure the application.
|
||||
See the https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-use-tomcat-behind-a-proxy-server[Spring Boot documentation] for further details.
|
||||
|
||||
@@ -116,10 +116,3 @@ The Lambda DSL was created to accomplish to following goals:
|
||||
- Automatic indentation makes the configuration more readable.
|
||||
- The 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,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,23 +1,31 @@
|
||||
[[migration]]
|
||||
= Migrating to 6.2
|
||||
= Migrating to 6.0
|
||||
:spring-security-reference-base-url: https://docs.spring.io/spring-security/reference
|
||||
|
||||
This guide provides instructions for migrating from Spring Security 6.1 to Spring Security 6.2.
|
||||
The Spring Security team has prepared the 5.8 release to simplify upgrading to Spring Security 6.0.
|
||||
Use 5.8 and
|
||||
ifdef::spring-security-version[]
|
||||
{spring-security-reference-base-url}/5.8/migration/index.html[its preparation steps]
|
||||
endif::[]
|
||||
ifndef::spring-security-version[]
|
||||
its preparation steps
|
||||
endif::[]
|
||||
to simplify updating to 6.0.
|
||||
|
||||
== Update to Spring Security 6.2
|
||||
After updating to 5.8, follow this guide to perform any remaining migration or cleanup steps.
|
||||
|
||||
When updating to a new minor version, it is important that you are already using the latest patch release of the previous minor version.
|
||||
For example, if you are upgrading to Spring Security 6.2, you should already be using the latest patch release of Spring Security 6.1.
|
||||
This makes it easier to identify any changes that may have been introduced in the new minor version.
|
||||
And recall that if you run into trouble, the preparation guide includes opt-out steps to revert to 5.x behaviors.
|
||||
|
||||
Therefore, the first step is to ensure you are on the latest patch release of Spring Boot 3.1.
|
||||
Next, you should ensure you are on the latest patch release of Spring Security 6.1.
|
||||
Typically, the latest patch release of Spring Boot uses the latest patch release of Spring Security.
|
||||
== Update to Spring Security 6.0
|
||||
|
||||
With those two steps complete, you can now update to Spring Security 6.2.
|
||||
The first step is to ensure you are the latest patch release of Spring Boot 3.0.
|
||||
Next, you should ensure you are on the latest patch release of Spring Security 6.0.
|
||||
For directions, on how to update to Spring Security 6.0 visit the xref:getting-spring-security.adoc[] section of the reference guide.
|
||||
|
||||
== Quick Reference
|
||||
== Update Package Names
|
||||
|
||||
The following list provide a quick reference for the changes that are described in this guide.
|
||||
Now that you are updated, you need to change your `javax` imports to `jakarta` imports.
|
||||
|
||||
- xref:migration/authorization.adoc#compile-with-parameters[You are using method parameter names in `@PreAuthorize`, `@PostAuthorize`, or any other method security annotations]
|
||||
== Perform Application-Specific Steps
|
||||
|
||||
Next, there are steps you need to perform based on whether it is a xref:migration/servlet/index.adoc[Servlet] or xref:migration/reactive.adoc[Reactive] application.
|
||||
|
||||
100
docs/modules/ROOT/pages/migration/reactive.adoc
Normal file
100
docs/modules/ROOT/pages/migration/reactive.adoc
Normal file
@@ -0,0 +1,100 @@
|
||||
= Reactive
|
||||
|
||||
If you have already performed the xref:migration/index.adoc[initial migration steps] for your Reactive application, you're now ready to perform steps specific to Reactive applications.
|
||||
|
||||
== Use `AuthorizationManager` for Method Security
|
||||
|
||||
In 6.0, `@EnableReactiveMethodSecurity` defaults `useAuthorizationManager` to `true`.
|
||||
So, to complete migration, {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurity.html[`@EnableReactiveMethodSecurity`] remove the `useAuthorizationManager` attribute:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity(useAuthorizationManager = true)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity(useAuthorizationManager = true)
|
||||
----
|
||||
======
|
||||
|
||||
changes to:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity
|
||||
----
|
||||
======
|
||||
|
||||
== Propagate ``AuthenticationServiceException``s
|
||||
|
||||
{security-api-url}org/springframework/security/web/server/authentication/AuthenticationWebFilter.html[`AuthenticationWebFilter`] propagates {security-api-url}org/springframework/security/authentication/AuthenticationServiceException.html[``AuthenticationServiceException``]s to the {security-api-url}org/springframework/security/web/server/ServerAuthenticationEntryPoint.html[`ServerAuthenticationEntryPoint`].
|
||||
Because ``AuthenticationServiceException``s represent a server-side error instead of a client-side error, in 6.0, this changes to propagate them to the container.
|
||||
|
||||
So, if you opted into this behavior by setting `rethrowAuthenticationServiceException` too `true`, you can now remove it like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
AuthenticationFailureHandler bearerFailureHandler = new ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint);
|
||||
bearerFailureHandler.setRethrowAuthenticationServiceException(true);
|
||||
AuthenticationFailureHandler basicFailureHandler = new ServerAuthenticationEntryPointFailureHandler(basicEntryPoint);
|
||||
basicFailureHandler.setRethrowAuthenticationServiceException(true);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val bearerFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint)
|
||||
bearerFailureHandler.setRethrowAuthenticationServiceException(true)
|
||||
val basicFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(basicEntryPoint)
|
||||
basicFailureHandler.setRethrowAuthenticationServiceException(true)
|
||||
----
|
||||
======
|
||||
|
||||
changes to:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
AuthenticationFailureHandler bearerFailureHandler = new ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint);
|
||||
AuthenticationFailureHandler basicFailureHandler = new ServerAuthenticationEntryPointFailureHandler(basicEntryPoint);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val bearerFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint)
|
||||
val basicFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(basicEntryPoint)
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If you configured the `ServerAuthenticationFailureHandler` only for the purpose of updating to 6.0, you can remove it completely.
|
||||
====
|
||||
187
docs/modules/ROOT/pages/migration/servlet/authentication.adoc
Normal file
187
docs/modules/ROOT/pages/migration/servlet/authentication.adoc
Normal file
@@ -0,0 +1,187 @@
|
||||
= 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.
|
||||
117
docs/modules/ROOT/pages/migration/servlet/authorization.adoc
Normal file
117
docs/modules/ROOT/pages/migration/servlet/authorization.adoc
Normal file
@@ -0,0 +1,117 @@
|
||||
= 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, {security-api-url}org/springframework/security/config/annotation/web/configurers/AbstractInterceptUrlConfigurer.AbstractInterceptUrlRegistry.html#filterSecurityInterceptorOncePerRequest(boolean)[`authorizeRequests#filterSecurityInterceptorOncePerRequest`] defaults to `false` and 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"`
|
||||
====
|
||||
44
docs/modules/ROOT/pages/migration/servlet/exploits.adoc
Normal file
44
docs/modules/ROOT/pages/migration/servlet/exploits.adoc
Normal file
@@ -0,0 +1,44 @@
|
||||
= 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.
|
||||
4
docs/modules/ROOT/pages/migration/servlet/index.adoc
Normal file
4
docs/modules/ROOT/pages/migration/servlet/index.adoc
Normal file
@@ -0,0 +1,4 @@
|
||||
= Servlet Migrations
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
If you have already performed the xref:migration/index.adoc[initial migration steps] for your Servlet application, you're now ready to perform steps specific to Servlet applications.
|
||||
@@ -0,0 +1,49 @@
|
||||
= 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[]
|
||||
@@ -700,5 +700,111 @@ For MAC based algorithms such as `HS256`, `HS384` or `HS512`, the `client-secret
|
||||
[TIP]
|
||||
If more than one `ClientRegistration` is configured for OpenID Connect 1.0 Authentication, the JWS algorithm resolver may evaluate the provided `ClientRegistration` to determine which algorithm to return.
|
||||
|
||||
|
||||
[[webflux-oauth2-login-advanced-oidc-logout]]
|
||||
Then, you can proceed to configure xref:reactive/oauth2/login/logout.adoc[logout].
|
||||
== OpenID Connect 1.0 Logout
|
||||
|
||||
OpenID Connect Session Management 1.0 allows the ability to log out the End-User at the Provider using the Client.
|
||||
One of the strategies available is https://openid.net/specs/openid-connect-rpinitiated-1_0.html[RP-Initiated Logout].
|
||||
|
||||
If the OpenID Provider supports both Session Management and https://openid.net/specs/openid-connect-discovery-1_0.html[Discovery], the client may obtain the `end_session_endpoint` `URL` from the OpenID Provider's https://openid.net/specs/openid-connect-session-1_0.html#OPMetadata[Discovery Metadata].
|
||||
This can be achieved by configuring the `ClientRegistration` with the `issuer-uri`, as in the following example:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
okta:
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
...
|
||||
provider:
|
||||
okta:
|
||||
issuer-uri: https://dev-1234.oktapreview.com
|
||||
----
|
||||
|
||||
...and the `OidcClientInitiatedServerLogoutSuccessHandler`, which implements RP-Initiated Logout, may be configured as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class OAuth2LoginSecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
.authorizeExchange(authorize -> authorize
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.oauth2Login(withDefaults())
|
||||
.logout(logout -> logout
|
||||
.logoutSuccessHandler(oidcLogoutSuccessHandler())
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
|
||||
OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
|
||||
new OidcClientInitiatedServerLogoutSuccessHandler(this.clientRegistrationRepository);
|
||||
|
||||
// Sets the location that the End-User's User Agent will be redirected to
|
||||
// after the logout has been performed at the Provider
|
||||
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
|
||||
|
||||
return oidcLogoutSuccessHandler;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
class OAuth2LoginSecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private lateinit var clientRegistrationRepository: ReactiveClientRegistrationRepository
|
||||
|
||||
@Bean
|
||||
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2Login { }
|
||||
logout {
|
||||
logoutSuccessHandler = oidcLogoutSuccessHandler()
|
||||
}
|
||||
}
|
||||
|
||||
return http.build()
|
||||
}
|
||||
|
||||
private fun oidcLogoutSuccessHandler(): ServerLogoutSuccessHandler {
|
||||
val oidcLogoutSuccessHandler = OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)
|
||||
|
||||
// Sets the location that the End-User's User Agent will be redirected to
|
||||
// after the logout has been performed at the Provider
|
||||
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
|
||||
return oidcLogoutSuccessHandler
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `OidcClientInitiatedServerLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
|
||||
If used, the application's base URL, like `https://app.example.org`, will replace it at request time.
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
= OIDC Logout
|
||||
|
||||
Once an end user is able to login to your application, it's important to consider how they will log out.
|
||||
|
||||
Generally speaking, there are three use cases for you to consider:
|
||||
|
||||
1. I want to perform only a local logout
|
||||
2. I want to log out both my application and the OIDC Provider, initiated by my application
|
||||
3. I want to log out both my application and the OIDC Provider, initiated by the OIDC Provider
|
||||
|
||||
[[configure-local-logout]]
|
||||
== Local Logout
|
||||
|
||||
To perform a local logout, no special OIDC configuration is needed.
|
||||
Spring Security automatically stands up a local logout endpoint, which you can xref:reactive/authentication/logout.adoc[configure through the `logout()` DSL].
|
||||
|
||||
[[configure-client-initiated-oidc-logout]]
|
||||
[[oauth2login-advanced-oidc-logout]]
|
||||
== OpenID Connect 1.0 Client-Initiated Logout
|
||||
|
||||
OpenID Connect Session Management 1.0 allows the ability to log out the end user at the Provider by using the Client.
|
||||
One of the strategies available is https://openid.net/specs/openid-connect-rpinitiated-1_0.html[RP-Initiated Logout].
|
||||
|
||||
If the OpenID Provider supports both Session Management and https://openid.net/specs/openid-connect-discovery-1_0.html[Discovery], the client can obtain the `end_session_endpoint` `URL` from the OpenID Provider's https://openid.net/specs/openid-connect-session-1_0.html#OPMetadata[Discovery Metadata].
|
||||
You can do so by configuring the `ClientRegistration` with the `issuer-uri`, as follows:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
okta:
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
...
|
||||
provider:
|
||||
okta:
|
||||
issuer-uri: https://dev-1234.oktapreview.com
|
||||
----
|
||||
|
||||
Also, you should configure `OidcClientInitiatedServerLogoutSuccessHandler`, which implements RP-Initiated Logout, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class OAuth2LoginSecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeExchange((authorize) -> authorize
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.oauth2Login(withDefaults())
|
||||
.logout((logout) -> logout
|
||||
.logoutSuccessHandler(oidcLogoutSuccessHandler())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
|
||||
OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
|
||||
new OidcClientInitiatedServerLogoutSuccessHandler(this.clientRegistrationRepository);
|
||||
|
||||
// Sets the location that the End-User's User Agent will be redirected to
|
||||
// after the logout has been performed at the Provider
|
||||
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
|
||||
|
||||
return oidcLogoutSuccessHandler;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
class OAuth2LoginSecurityConfig {
|
||||
@Autowired
|
||||
private lateinit var clientRegistrationRepository: ReactiveClientRegistrationRepository
|
||||
|
||||
@Bean
|
||||
open fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2Login { }
|
||||
logout {
|
||||
logoutSuccessHandler = oidcLogoutSuccessHandler()
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
private fun oidcLogoutSuccessHandler(): ServerLogoutSuccessHandler {
|
||||
val oidcLogoutSuccessHandler = OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)
|
||||
|
||||
// Sets the location that the End-User's User Agent will be redirected to
|
||||
// after the logout has been performed at the Provider
|
||||
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
|
||||
return oidcLogoutSuccessHandler
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`OidcClientInitiatedServerLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
|
||||
If used, the application's base URL, such as `https://app.example.org`, replaces it at request time.
|
||||
====
|
||||
|
||||
[[configure-provider-initiated-oidc-logout]]
|
||||
== OpenID Connect 1.0 Back-Channel Logout
|
||||
|
||||
OpenID Connect Session Management 1.0 allows the ability to log out the end user at the Client by having the Provider make an API call to the Client.
|
||||
This is referred to as https://openid.net/specs/openid-connect-backchannel-1_0.html[OIDC Back-Channel Logout].
|
||||
|
||||
To enable this, you can stand up the Back-Channel Logout endpoint in the DSL like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source=java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeExchange((authorize) -> authorize
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.oauth2Login(withDefaults())
|
||||
.oidcLogout((logout) -> logout
|
||||
.backChannel(Customizer.withDefaults())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source=kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2Login { }
|
||||
oidcLogout {
|
||||
backChannel { }
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
And that's it!
|
||||
|
||||
This will stand up the endpoint `+/logout/connect/back-channel/{registrationId}+` which the OIDC Provider can request to invalidate a given session of an end user in your application.
|
||||
|
||||
[NOTE]
|
||||
`oidcLogout` requires that `oauth2Login` also be configured.
|
||||
|
||||
[NOTE]
|
||||
`oidcLogout` requires that the session cookie be called `JSESSIONID` in order to correctly log out each session through a backchannel.
|
||||
|
||||
=== Back-Channel Logout Architecture
|
||||
|
||||
Consider a `ClientRegistration` whose identifier is `registrationId`.
|
||||
|
||||
The overall flow for a Back-Channel logout is like this:
|
||||
|
||||
1. At login time, Spring Security correlates the ID Token, CSRF Token, and Provider Session ID (if any) to your application's session id in its `ReactiveOidcSessionStrategy` implementation.
|
||||
2. Then at logout time, your OIDC Provider makes an API call to `/logout/connect/back-channel/registrationId` including a Logout Token that indicates either the `sub` (the End User) or the `sid` (the Provider Session ID) to logout.
|
||||
3. Spring Security validates the token's signature and claims.
|
||||
4. If the token contains a `sid` claim, then only the Client's session that correlates to that provider session is terminated.
|
||||
5. Otherwise, if the token contains a `sub` claim, then all that Client's sessions for that End User are terminated.
|
||||
|
||||
[NOTE]
|
||||
Remember that Spring Security's OIDC support is multi-tenant.
|
||||
This means that it will only terminate sessions whose Client matches the `aud` claim in the Logout Token.
|
||||
|
||||
=== Customizing the OIDC Provider Session Strategy
|
||||
|
||||
By default, Spring Security stores in-memory all links between the OIDC Provider session and the Client session.
|
||||
|
||||
There are a number of circumstances, like a clustered application, where it would be nice to store this instead in a separate location, like a database.
|
||||
|
||||
You can achieve this by configuring a custom `ReactiveOidcSessionStrategy`, like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source=java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
public final class MySpringDataOidcSessionStrategy implements OidcSessionStrategy {
|
||||
private final OidcProviderSessionRepository sessions;
|
||||
|
||||
// ...
|
||||
|
||||
@Override
|
||||
public void saveSessionInformation(OidcSessionInformation info) {
|
||||
this.sessions.save(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OidcSessionInformation(String clientSessionId) {
|
||||
return this.sessions.removeByClientSessionId(clientSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<OidcSessionInformation> removeSessionInformation(OidcLogoutToken token) {
|
||||
return token.getSessionId() != null ?
|
||||
this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
|
||||
this.sessions.removeBySubjectAndIssuerAndAudience(...);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source=kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class MySpringDataOidcSessionStrategy: ReactiveOidcSessionStrategy {
|
||||
val sessions: OidcProviderSessionRepository
|
||||
|
||||
// ...
|
||||
|
||||
@Override
|
||||
fun saveSessionInformation(info: OidcSessionInformation): Mono<Void> {
|
||||
return this.sessions.save(info)
|
||||
}
|
||||
|
||||
@Override
|
||||
fun removeSessionInformation(clientSessionId: String): Mono<OidcSessionInformation> {
|
||||
return this.sessions.removeByClientSessionId(clientSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
fun removeSessionInformation(token: OidcLogoutToken): Flux<OidcSessionInformation> {
|
||||
return token.getSessionId() != null ?
|
||||
this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
|
||||
this.sessions.removeBySubjectAndIssuerAndAudience(...);
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -165,13 +165,11 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.pathMatchers("/message/**").access(hasScope("message:read"))
|
||||
.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
@@ -185,13 +183,11 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope
|
||||
|
||||
@Bean
|
||||
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize("/message/**", hasScope("message:read"))
|
||||
authorize("/message/**", hasAuthority("SCOPE_message:read"))
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
@@ -686,14 +682,12 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.mvcMatchers("/contacts/**").access(hasScope("contacts"))
|
||||
.mvcMatchers("/messages/**").access(hasScope("messages"))
|
||||
.mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
|
||||
.mvcMatchers("/messages/**").hasAuthority("SCOPE_messages")
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt);
|
||||
@@ -705,14 +699,12 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope
|
||||
|
||||
@Bean
|
||||
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize("/contacts/**", hasScope("contacts"))
|
||||
authorize("/messages/**", hasScope("messages"))
|
||||
authorize("/contacts/**", hasAuthority("SCOPE_contacts"))
|
||||
authorize("/messages/**", hasAuthority("SCOPE_messages"))
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
|
||||
@@ -23,8 +23,8 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver
|
||||
.fromTrustedIssuers("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");
|
||||
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver
|
||||
("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");
|
||||
|
||||
http
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
@@ -39,8 +39,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver
|
||||
.fromTrustedIssuers("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")
|
||||
val customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")
|
||||
|
||||
return http {
|
||||
authorizeExchange {
|
||||
|
||||
@@ -214,8 +214,6 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class MyCustomSecurityConfiguration {
|
||||
@@ -223,7 +221,7 @@ public class MyCustomSecurityConfiguration {
|
||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.pathMatchers("/messages/**").access(hasScope("message:read"))
|
||||
.pathMatchers("/messages/**").hasAuthority("SCOPE_message:read")
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
@@ -240,13 +238,11 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope
|
||||
|
||||
@Bean
|
||||
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize("/messages/**", hasScope("message:read"))
|
||||
authorize("/messages/**", hasAuthority("SCOPE_message:read"))
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
@@ -446,8 +442,6 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class MappedAuthorities {
|
||||
@@ -455,8 +449,8 @@ public class MappedAuthorities {
|
||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
.authorizeExchange(exchange -> exchange
|
||||
.pathMatchers("/contacts/**").access(hasScope("contacts"))
|
||||
.pathMatchers("/messages/**").access(hasScope("messages"))
|
||||
.pathMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
|
||||
.pathMatchers("/messages/**").hasAuthority("SCOPE_messages")
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::opaqueToken);
|
||||
@@ -469,14 +463,12 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope
|
||||
|
||||
@Bean
|
||||
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize("/contacts/**", hasScope("contacts"))
|
||||
authorize("/messages/**", hasScope("messages"))
|
||||
authorize("/contacts/**", hasAuthority("SCOPE_contacts"))
|
||||
authorize("/messages/**", hasAuthority("SCOPE_messages"))
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
|
||||
@@ -6,4 +6,4 @@ This appendix provides a reference to the elements available in the security nam
|
||||
If you haven't used the namespace before, please read the xref:servlet/configuration/xml-namespace.adoc#ns-config[introductory chapter] on namespace configuration, as this is intended as a supplement to the information there.
|
||||
Using a good quality XML editor while editing a configuration based on the schema is recommended as this will provide contextual information on which elements and attributes are available as well as comments explaining their purpose.
|
||||
The namespace is written in https://relaxng.org/[RELAX NG] Compact format and later converted into an XSD schema.
|
||||
If you are familiar with this format, you may wish to examine the https://raw.githubusercontent.com/spring-projects/spring-security/main/config/src/main/resources/org/springframework/security/config/spring-security-6.2.rnc[schema file] directly.
|
||||
If you are familiar with this format, you may wish to examine the https://raw.githubusercontent.com/spring-projects/spring-security/main/config/src/main/resources/org/springframework/security/config/spring-security-6.1.rnc[schema file] directly.
|
||||
|
||||
@@ -24,8 +24,6 @@ When you include {spring-boot-reference-url}using.html#using.build-systems.start
|
||||
If you request `GET /logout`, then Spring Security displays a logout confirmation page.
|
||||
Aside from providing a valuable double-checking mechanism for the user, it also provides a simple way to provide xref:servlet/exploits/csrf.adoc[the needed CSRF token] to `POST /logout`.
|
||||
|
||||
Please note that if xref:servlet/exploits/csrf.adoc[CSRF protection] is disabled in configuration, no logout confirmation page is shown to the user and the logout is performed directly.
|
||||
|
||||
[TIP]
|
||||
In your application it is not necessary to use `GET /logout` to perform a logout.
|
||||
So long as xref:servlet/exploits/csrf.adoc[the needed CSRF token] is present in the request, your application can simply `POST /logout` to induce a logout.
|
||||
@@ -82,7 +80,7 @@ Xml::
|
||||
and no authorization changes are necessary since it simply adjusts the `LogoutFilter`.
|
||||
|
||||
[[permit-logout-endpoints]]
|
||||
However, if you stand up your own logout success endpoint (or in a rare case, <<creating-custom-logout-endpoint, your own logout endpoint>>), say using {spring-framework-reference-url}web.html#spring-web[Spring MVC], you will need to permit it in Spring Security.
|
||||
However, if you stand up your own logout success endpoint (or in a rare case, <<creating-custom-logout-endpoint, your own logout endpoint>>), say using {spring-framework-reference-url}web.html#spring-web[Spring MVC], you will need permit it in Spring Security.
|
||||
This is because Spring MVC processes your request after Spring Security does.
|
||||
|
||||
You can do this using `authorizeHttpRequests` or `<intercept-url>` like so:
|
||||
|
||||
@@ -80,7 +80,7 @@ If you try to use any of these methods, an exception will be thrown.
|
||||
|
||||
By default, Spring Security stores the security context for you in the HTTP session. However, here are several reasons you may want to customize that:
|
||||
|
||||
* You may want to call individual setters on the `HttpSessionSecurityContextRepository` instance
|
||||
* You may want call individual setters on the `HttpSessionSecurityContextRepository` instance
|
||||
* You may want to store the security context in a cache or database to enable horizontal scaling
|
||||
|
||||
First, you need to create an implementation of `SecurityContextRepository` or use an existing implementation like `HttpSessionSecurityContextRepository`, then you can set it in `HttpSecurity`.
|
||||
|
||||
@@ -81,7 +81,7 @@ This means that Spring Security's xref:servlet/authentication/index.adoc[authent
|
||||
If you add filters of your own before the `AuthorizationFilter`, they will also not require authorization; otherwise, they will.
|
||||
|
||||
A place where this typically becomes important is when you are adding {spring-framework-reference-url}web.html#spring-web[Spring MVC] endpoints.
|
||||
Because they are executed by the {spring-framework-reference-url}web.html#mvc-servlet[`DispatcherServlet`] and this comes after the `AuthorizationFilter`, your endpoints need to be <<authorizing-endpoints,included in `authorizeHttpRequests` to be permitted>>.
|
||||
Because they are executed by the {spring-framework-reference-url}web.html#mvc-servlet[`DispatcherServlet`] and this comes after the `AuthorizationFilter`, you're endpoints need to be <<authorizing-endpoints,included in `authorizeHttpRequests` to be permitted>>.
|
||||
|
||||
=== All Dispatches Are Authorized
|
||||
|
||||
@@ -571,156 +571,70 @@ http {
|
||||
----
|
||||
====
|
||||
|
||||
[[match-by-servlet-path]]
|
||||
[[mvc-not-default-servlet]]
|
||||
[[match-by-mvc]]
|
||||
=== Matching by Servlet Pattern
|
||||
=== Using an MvcRequestMatcher
|
||||
|
||||
Generally speaking, you can use `requestMatchers(String...)` and `requestMatchers(HttpMethod, String...)` as demonstrated above.
|
||||
Generally speaking, you can use `requestMatchers(String)` as demonstrated above.
|
||||
|
||||
However, if you map Spring MVC to a different servlet path, then you need to account for that in your security configuration.
|
||||
|
||||
For example, if Spring MVC is mapped to `/mvc` instead of `/` (the default), then you may have an endpoint like `/mvc/my/controller` that you want to authorize.
|
||||
For example, if Spring MVC is mapped to `/spring-mvc` instead of `/` (the default), then you may have an endpoint like `/spring-mvc/my/controller` that you want to authorize.
|
||||
|
||||
If you have multiple servlets, and `DispatcherServlet` is mapped in this way, you'll see an error that's something like this:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
This method cannot decide whether these patterns are Spring MVC patterns or not
|
||||
|
||||
...
|
||||
|
||||
For your reference, here is your servlet configuration: {default=[/], dispatcherServlet=[/mvc/*]}
|
||||
|
||||
To address this, you need to specify the servlet path or pattern for each endpoint.
|
||||
You can use .forServletPattern in conjunction with requestMatchers do to this
|
||||
----
|
||||
|
||||
You can use `.forServletPattern` (or construct your own `MvcRequestMatcher` instance) to split the servlet path and the controller path in your configuration, like so:
|
||||
You need to use `MvcRequestMatcher` to split the servlet path and the controller path in your configuration like so:
|
||||
|
||||
.Match by MvcRequestMatcher
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
|
||||
return new MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain appEndpoints(HttpSecurity http, MvcRequestMatcher.Builder mvc) {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.forServletPattern("/mvc/*", (mvc) -> mvc
|
||||
.requestMatchers("/my/resource/**").hasAuthority("resource:read")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.requestMatchers(mvc.pattern("/my/controller/**")).hasAuthority("controller")
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
where `/mvc/*` is the matching pattern in your servlet configuration listed in the error message.
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun mvc(introspector: HandlerMappingIntrospector): MvcRequestMatcher.Builder =
|
||||
MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");
|
||||
|
||||
@Bean
|
||||
fun appEndpoints(http: HttpSecurity, mvc: MvcRequestMatcher.Builder): SecurityFilterChain =
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(mvc.pattern("/my/controller/**"), hasAuthority("controller"))
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
<intercept-url servlet-path="/spring-mvc" pattern="/my/controller/**" access="hasAuthority('controller')"/>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
This need can arise in at least two different ways:
|
||||
|
||||
* If you use the `spring.mvc.servlet.path` Boot property to change the default path (`/`) to something else
|
||||
* If you register more than one Spring MVC `DispatcherServlet` (thus requiring that one of them not be the default servlet)
|
||||
|
||||
Note that when either of these cases come up, all URIs need to be fully-qualified as above.
|
||||
|
||||
For example, consider a more sophisticated setup where you have Spring MVC resources mapped to `/mvc/*` and Spring Boot H2 Console mapped to `/h2-console/*`.
|
||||
In that case, each URI can be made absolute, listing the servlet path like so:
|
||||
|
||||
.Match by Servlet Path
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.forServletPattern("/mvc/*", (mvc) -> mvc
|
||||
.requestMatchers("/my/resource/**").hasAuthority("resource:read")
|
||||
)
|
||||
.forServletPattern("/h2-console/*", (h2) -> h2
|
||||
.anyRequest().hasAuthority("h2")
|
||||
)
|
||||
)
|
||||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Alternatively, you can do one of three things to remove the need to disambiguate:
|
||||
|
||||
1. Always deploy `DispatcherServlet` to `/` (the default behavior)
|
||||
+
|
||||
When `DispatcherServlet` is mapped to `/`, it's clear that all the URIs supplied in `requestMatchers(String)` are absolute URIs.
|
||||
Because of that, there is no ambiguity when interpreting them.
|
||||
+
|
||||
2. Remove all other servlets
|
||||
+
|
||||
When there is only `DispatcherServlet`, it's clear that all the URIs supplied in `requestMatchers(String)` are relative to the Spring MVC configuration.
|
||||
Because of that, there is no ambiguity when interpreting them.
|
||||
|
||||
At times, servlet containers add other servlets by default that you aren't actually using.
|
||||
So, if these aren't needed, remove them, bringing you down to just `DispatcherServlet`.
|
||||
+
|
||||
3. Create an `HttpRequestHandler` so that `DispatcherServlet` dispatches to your servlets instead of your servlet container.
|
||||
+
|
||||
If you are deploying Spring MVC to a separate path to allow your container to serve static resources, consider instead {spring-framework-reference-url}web/webmvc/mvc-config/default-servlet-handler.html#page-title[notifying Spring MVC about this].
|
||||
Or, if you have a custom servlet, publishing {spring-framework-api-url}org/springframework/web/servlet/mvc/HttpRequestHandlerAdapter.html[a custom `HttpRequestHandler` bean within {spring-framework-api-url}org/springframework/web/servlet/DispatcherServlet.html[the `DispatcherServlet` configuration] instead.
|
||||
+
|
||||
|
||||
=== Matching by the Default Servlet
|
||||
|
||||
You can also match more generally by the matching pattern specified in your servlet configuration.
|
||||
|
||||
For example, to match the default servlet (whichever servlet is mapped to `/`), use `forServletPattern` like so:
|
||||
|
||||
.Match by the Default Servlet
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.forServletPattern("/", (root) -> root
|
||||
.requestMatchers("/my/resource/**").hasAuthority("resource:read")
|
||||
)
|
||||
)
|
||||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Such will match on requests that the servlet container matches to your default servlet that start with the URI `/my/resource`.
|
||||
|
||||
=== Matching by an Extension Servlet
|
||||
|
||||
Or, to match to an extension servlet (like a servlet mapped to `*.jsp`), use `forServletPattern` as follows:
|
||||
|
||||
.Match by an Extension Servlet
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.forServletPattern("*.jsp", (jsp) -> jsp
|
||||
.requestMatchers("/my/resource/**").hasAuthority("resource:read")
|
||||
)
|
||||
)
|
||||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Such will match on requests that the servlet container matches to your `*.jsp` servlet that start with the URI `/my/resource` (for example a request like `/my/resource/page.jsp`).
|
||||
* If you register more than one Spring MVC `DispatcherServlet` (thus requiring that one of them not be the default path)
|
||||
|
||||
[[match-by-custom]]
|
||||
=== Using a Custom Matcher
|
||||
|
||||
@@ -227,11 +227,7 @@ This configuration is considered after `apiFilterChain`, since it has an `@Order
|
||||
|
||||
You can provide your own custom DSLs in Spring Security:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
|
||||
private boolean flag;
|
||||
@@ -264,38 +260,6 @@ public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurit
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class MyCustomDsl : AbstractHttpConfigurer<MyCustomDsl, HttpSecurity>() {
|
||||
var flag: Boolean = false
|
||||
|
||||
override fun init(http: HttpSecurity) {
|
||||
// any method that adds another configurer
|
||||
// must be done in the init method
|
||||
http.csrf().disable()
|
||||
}
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
|
||||
|
||||
// here we lookup from the ApplicationContext. You can also just create a new instance.
|
||||
val myFilter: MyFilter = context.getBean(MyFilter::class.java)
|
||||
myFilter.setFlag(flag)
|
||||
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter::class.java)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun customDsl(): MyCustomDsl {
|
||||
return MyCustomDsl()
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This is actually how methods like `HttpSecurity.authorizeRequests()` are implemented.
|
||||
@@ -303,11 +267,7 @@ This is actually how methods like `HttpSecurity.authorizeRequests()` are impleme
|
||||
|
||||
You can then use the custom DSL:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@@ -315,37 +275,15 @@ public class Config {
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
|
||||
.apply(customDsl())
|
||||
.flag(true)
|
||||
)
|
||||
// ...
|
||||
.and()
|
||||
...;
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class Config {
|
||||
|
||||
@Bean
|
||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http
|
||||
.with(MyCustomDsl.customDsl()) {
|
||||
flag = true
|
||||
}
|
||||
// ...
|
||||
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The code is invoked in the following order:
|
||||
|
||||
* Code in the `Config.filterChain` method is invoked
|
||||
@@ -363,50 +301,21 @@ org.springframework.security.config.annotation.web.configurers.AbstractHttpConfi
|
||||
|
||||
You can also explicit disable the default:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class Config {
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
|
||||
.disable()
|
||||
)
|
||||
.apply(customDsl()).disable()
|
||||
...;
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class Config {
|
||||
|
||||
@Bean
|
||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http
|
||||
.with(MyCustomDsl.customDsl()) {
|
||||
disable()
|
||||
}
|
||||
// ...
|
||||
return http.build()
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[post-processing-configured-objects]]
|
||||
== Post Processing Configured Objects
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ The following is an overview of the aspects of CSRF protection that have changed
|
||||
The changes in Spring Security 6 require additional configuration for single-page applications, and as such you may find the <<csrf-integration-javascript-spa>> section particularly useful.
|
||||
====
|
||||
|
||||
See the https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html[Exploit Protection] section of the https://docs.spring.io/spring-security/reference/5.8/migration/index.html[Migration] chapter for more information on migrating a Spring Security 5 application.
|
||||
See the xref:migration/servlet/exploits.adoc[Exploit Protection] section of the xref:migration/servlet/index.adoc[Migration] chapter for more information on migrating a Spring Security 5 application.
|
||||
|
||||
[[csrf-token-repository]]
|
||||
== Persisting the `CsrfToken`
|
||||
@@ -1609,4 +1609,4 @@ You can find more information in the https://docs.spring.io/spring/docs/current/
|
||||
[[csrf-further-reading]]
|
||||
== Further Reading
|
||||
|
||||
Now that you have reviewed CSRF protection, consider learning more about xref:servlet/exploits/index.adoc[exploit protection] including xref:servlet/exploits/headers.adoc[secure headers] and the xref:servlet/exploits/firewall.adoc[HTTP firewall] or move on to learning how to xref:servlet/test/index.adoc[test] your application.
|
||||
Now that you have reviewed CSRF protection, consider learning more about xref:servlet/exploits/index.adoc[exploit protection] including xref:servlet/exploits/headers.adoc[secure headers] and the xref:servlet/exploits/firewall.adoc[HTTP firewall] or move on to learning how to xref:servlet/test/index.adoc[test] your application.
|
||||
@@ -111,7 +111,7 @@ XML::
|
||||
----
|
||||
<b:bean id="httpFirewall"
|
||||
class="org.springframework.security.web.firewall.StrictHttpFirewall"
|
||||
p:allowedHttpMethods="GET,POST"/>
|
||||
p:allowedHttpMethods="GET,HEAD"/>
|
||||
|
||||
<http-firewall ref="httpFirewall"/>
|
||||
----
|
||||
|
||||
@@ -6,8 +6,7 @@ CORS must be processed before Spring Security, because the pre-flight request do
|
||||
If the request does not contain any cookies and Spring Security is first, the request determines that the user is not authenticated (since there are no cookies in the request) and rejects it.
|
||||
|
||||
The easiest way to ensure that CORS is handled first is to use the `CorsFilter`.
|
||||
Users can integrate the `CorsFilter` with Spring Security by providing a `CorsConfigurationSource`.
|
||||
For example, the following will integrate CORS support within Spring Security:
|
||||
Users can integrate the `CorsFilter` with Spring Security by providing a `CorsConfigurationSource` that uses the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -15,14 +14,28 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
|
||||
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// by default uses a Bean by the name of corsConfigurationSource
|
||||
.cors(withDefaults())
|
||||
...
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
|
||||
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -30,14 +43,28 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun corsConfigurationSource(): CorsConfigurationSource {
|
||||
val configuration = CorsConfiguration()
|
||||
configuration.allowedOrigins = listOf("https://example.com")
|
||||
configuration.allowedMethods = listOf("GET", "POST")
|
||||
val source = UrlBasedCorsConfigurationSource()
|
||||
source.registerCorsConfiguration("/**", configuration)
|
||||
return source
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class WebSecurityConfig {
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
// by default uses a Bean by the name of corsConfigurationSource
|
||||
cors { }
|
||||
// ...
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun corsConfigurationSource(): CorsConfigurationSource {
|
||||
val configuration = CorsConfiguration()
|
||||
configuration.allowedOrigins = listOf("https://example.com")
|
||||
configuration.allowedMethods = listOf("GET", "POST")
|
||||
val source = UrlBasedCorsConfigurationSource()
|
||||
source.registerCorsConfiguration("/**", configuration)
|
||||
return source
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -110,76 +137,3 @@ The following listing does the same thing in XML:
|
||||
...
|
||||
</http>
|
||||
----
|
||||
|
||||
If you have more than one `CorsConfigurationSource` bean, Spring Security won't automatically configure CORS support for you, that is because it cannot decide which one to use.
|
||||
If you want to specify different `CorsConfigurationSource` for each `SecurityFilterChain`, you can pass it directly into the `.cors()` DSL.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.securityMatcher("/api/**")
|
||||
.cors((cors) -> cors
|
||||
.configurationSource(apiConfigurationSource())
|
||||
)
|
||||
...
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
public SecurityFilterChain myOtherFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.cors((cors) -> cors
|
||||
.configurationSource(myWebsiteConfigurationSource())
|
||||
)
|
||||
...
|
||||
return http.build();
|
||||
}
|
||||
|
||||
CorsConfigurationSource apiConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(Arrays.asList("https://api.example.com"));
|
||||
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
CorsConfigurationSource myWebsiteConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
|
||||
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun corsConfigurationSource(): CorsConfigurationSource {
|
||||
val configuration = CorsConfiguration()
|
||||
configuration.allowedOrigins = listOf("https://example.com")
|
||||
configuration.allowedMethods = listOf("GET", "POST")
|
||||
val source = UrlBasedCorsConfigurationSource()
|
||||
source.registerCorsConfiguration("/**", configuration)
|
||||
return source
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -58,7 +58,7 @@ Kotlin::
|
||||
@EnableWebSocketSecurity // <1> <2>
|
||||
open class WebSocketSecurityConfig { // <1> <2>
|
||||
@Bean
|
||||
fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
|
||||
fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
|
||||
messages.simpDestMatchers("/user/**").hasRole("USER") // <3>
|
||||
return messages.build()
|
||||
}
|
||||
@@ -108,7 +108,7 @@ Kotlin::
|
||||
@EnableWebSocketSecurity // <1> <2>
|
||||
open class WebSocketSecurityConfig {
|
||||
@Bean
|
||||
fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
|
||||
fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
|
||||
return AuthorityAuthorizationManager.hasRole("USER") // <3>
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ Kotlin::
|
||||
----
|
||||
@Configuration
|
||||
open class WebSocketSecurityConfig {
|
||||
fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
|
||||
fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?> {
|
||||
messages
|
||||
.nullDestMatcher().authenticated() // <1>
|
||||
.simpSubscribeDestMatchers("/user/queue/errors").permitAll() // <2>
|
||||
@@ -394,7 +394,7 @@ open class WebSocketSecurityConfig : WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
override fun configureClientInboundChannel(registration: ChannelRegistration) {
|
||||
var myAuthorizationRules: AuthorizationManager<Message<*>> = AuthenticatedAuthorizationManager.authenticated()
|
||||
var myAuthorizationRules: AuthorizationManager<Message<?>> = AuthenticatedAuthorizationManager.authenticated()
|
||||
var authz: AuthorizationChannelInterceptor = AuthorizationChannelInterceptor(myAuthorizationRules)
|
||||
var publisher: AuthorizationEventPublisher = SpringAuthorizationEventPublisher(this.context)
|
||||
authz.setAuthorizationEventPublisher(publisher)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -929,5 +929,111 @@ For MAC-based algorithms (such as `HS256`, `HS384`, or `HS512`), the `client-sec
|
||||
If more than one `ClientRegistration` is configured for OpenID Connect 1.0 Authentication, the JWS algorithm resolver may evaluate the provided `ClientRegistration` to determine which algorithm to return.
|
||||
====
|
||||
|
||||
|
||||
[[oauth2login-advanced-oidc-logout]]
|
||||
Then, you can proceed to configure xref:reactive/oauth2/login/logout.adoc[logout]
|
||||
== OpenID Connect 1.0 Logout
|
||||
|
||||
OpenID Connect Session Management 1.0 allows the ability to log out the end user at the Provider by using the Client.
|
||||
One of the strategies available is https://openid.net/specs/openid-connect-rpinitiated-1_0.html[RP-Initiated Logout].
|
||||
|
||||
If the OpenID Provider supports both Session Management and https://openid.net/specs/openid-connect-discovery-1_0.html[Discovery], the client can obtain the `end_session_endpoint` `URL` from the OpenID Provider's https://openid.net/specs/openid-connect-session-1_0.html#OPMetadata[Discovery Metadata].
|
||||
You can do so by configuring the `ClientRegistration` with the `issuer-uri`, as follows:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
okta:
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
...
|
||||
provider:
|
||||
okta:
|
||||
issuer-uri: https://dev-1234.oktapreview.com
|
||||
----
|
||||
|
||||
Also, you can configure `OidcClientInitiatedLogoutSuccessHandler`, which implements RP-Initiated Logout, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2Login(withDefaults())
|
||||
.logout(logout -> logout
|
||||
.logoutSuccessHandler(oidcLogoutSuccessHandler())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
private LogoutSuccessHandler oidcLogoutSuccessHandler() {
|
||||
OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
|
||||
new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
|
||||
|
||||
// Sets the location that the End-User's User Agent will be redirected to
|
||||
// after the logout has been performed at the Provider
|
||||
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
|
||||
|
||||
return oidcLogoutSuccessHandler;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class OAuth2LoginSecurityConfig {
|
||||
@Autowired
|
||||
private lateinit var clientRegistrationRepository: ClientRegistrationRepository
|
||||
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2Login { }
|
||||
logout {
|
||||
logoutSuccessHandler = oidcLogoutSuccessHandler()
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
private fun oidcLogoutSuccessHandler(): LogoutSuccessHandler {
|
||||
val oidcLogoutSuccessHandler = OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository)
|
||||
|
||||
// Sets the location that the End-User's User Agent will be redirected to
|
||||
// after the logout has been performed at the Provider
|
||||
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
|
||||
return oidcLogoutSuccessHandler
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`OidcClientInitiatedLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
|
||||
If used, the application's base URL, such as `https://app.example.org`, replaces it at request time.
|
||||
====
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
= OIDC Logout
|
||||
|
||||
Once an end user is able to login to your application, it's important to consider how they will log out.
|
||||
|
||||
Generally speaking, there are three use cases for you to consider:
|
||||
|
||||
1. I want to perform only a local logout
|
||||
2. I want to log out both my application and the OIDC Provider, initiated by my application
|
||||
3. I want to log out both my application and the OIDC Provider, initiated by the OIDC Provider
|
||||
|
||||
[[configure-local-logout]]
|
||||
== Local Logout
|
||||
|
||||
To perform a local logout, no special OIDC configuration is needed.
|
||||
Spring Security automatically stands up a local logout endpoint, which you can xref:servlet/authentication/logout.adoc[configure through the `logout()` DSL].
|
||||
|
||||
[[configure-client-initiated-oidc-logout]]
|
||||
== OpenID Connect 1.0 Client-Initiated Logout
|
||||
|
||||
OpenID Connect Session Management 1.0 allows the ability to log out the end user at the Provider by using the Client.
|
||||
One of the strategies available is https://openid.net/specs/openid-connect-rpinitiated-1_0.html[RP-Initiated Logout].
|
||||
|
||||
If the OpenID Provider supports both Session Management and https://openid.net/specs/openid-connect-discovery-1_0.html[Discovery], the client can obtain the `end_session_endpoint` `URL` from the OpenID Provider's https://openid.net/specs/openid-connect-session-1_0.html#OPMetadata[Discovery Metadata].
|
||||
You can do so by configuring the `ClientRegistration` with the `issuer-uri`, as follows:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
okta:
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
...
|
||||
provider:
|
||||
okta:
|
||||
issuer-uri: https://dev-1234.oktapreview.com
|
||||
----
|
||||
|
||||
Also, you should configure `OidcClientInitiatedLogoutSuccessHandler`, which implements RP-Initiated Logout, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2Login(withDefaults())
|
||||
.logout(logout -> logout
|
||||
.logoutSuccessHandler(oidcLogoutSuccessHandler())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
private LogoutSuccessHandler oidcLogoutSuccessHandler() {
|
||||
OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
|
||||
new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
|
||||
|
||||
// Sets the location that the End-User's User Agent will be redirected to
|
||||
// after the logout has been performed at the Provider
|
||||
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
|
||||
|
||||
return oidcLogoutSuccessHandler;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class OAuth2LoginSecurityConfig {
|
||||
@Autowired
|
||||
private lateinit var clientRegistrationRepository: ClientRegistrationRepository
|
||||
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2Login { }
|
||||
logout {
|
||||
logoutSuccessHandler = oidcLogoutSuccessHandler()
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
private fun oidcLogoutSuccessHandler(): LogoutSuccessHandler {
|
||||
val oidcLogoutSuccessHandler = OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository)
|
||||
|
||||
// Sets the location that the End-User's User Agent will be redirected to
|
||||
// after the logout has been performed at the Provider
|
||||
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
|
||||
return oidcLogoutSuccessHandler
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`OidcClientInitiatedLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
|
||||
If used, the application's base URL, such as `https://app.example.org`, replaces it at request time.
|
||||
====
|
||||
|
||||
[[configure-provider-initiated-oidc-logout]]
|
||||
== OpenID Connect 1.0 Back-Channel Logout
|
||||
|
||||
OpenID Connect Session Management 1.0 allows the ability to log out the end user at the Client by having the Provider make an API call to the Client.
|
||||
This is referred to as https://openid.net/specs/openid-connect-backchannel-1_0.html[OIDC Back-Channel Logout].
|
||||
|
||||
To enable this, you can stand up the Back-Channel Logout endpoint in the DSL like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source=java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2Login(withDefaults())
|
||||
.oidcLogout((logout) -> logout
|
||||
.backChannel(Customizer.withDefaults())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source=kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2Login { }
|
||||
oidcLogout {
|
||||
backChannel { }
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
And that's it!
|
||||
|
||||
This will stand up the endpoint `+/logout/connect/back-channel/{registrationId}+` which the OIDC Provider can request to invalidate a given session of an end user in your application.
|
||||
|
||||
[NOTE]
|
||||
`oidcLogout` requires that `oauth2Login` also be configured.
|
||||
|
||||
[NOTE]
|
||||
`oidcLogout` requires that the session cookie be called `JSESSIONID` in order to correctly log out each session through a backchannel.
|
||||
|
||||
=== Back-Channel Logout Architecture
|
||||
|
||||
Consider a `ClientRegistration` whose identifier is `registrationId`.
|
||||
|
||||
The overall flow for a Back-Channel logout is like this:
|
||||
|
||||
1. At login time, Spring Security correlates the ID Token, CSRF Token, and Provider Session ID (if any) to your application's session id in its `OidcSessionStrategy` implementation.
|
||||
2. Then at logout time, your OIDC Provider makes an API call to `/logout/connect/back-channel/registrationId` including a Logout Token that indicates either the `sub` (the End User) or the `sid` (the Provider Session ID) to logout.
|
||||
3. Spring Security validates the token's signature and claims.
|
||||
4. If the token contains a `sid` claim, then only the Client's session that correlates to that provider session is terminated.
|
||||
5. Otherwise, if the token contains a `sub` claim, then all that Client's sessions for that End User are terminated.
|
||||
|
||||
[NOTE]
|
||||
Remember that Spring Security's OIDC support is multi-tenant.
|
||||
This means that it will only terminate sessions whose Client matches the `aud` claim in the Logout Token.
|
||||
|
||||
=== Customizing the OIDC Provider Session Strategy
|
||||
|
||||
By default, Spring Security stores in-memory all links between the OIDC Provider session and the Client session.
|
||||
|
||||
There are a number of circumstances, like a clustered application, where it would be nice to store this instead in a separate location, like a database.
|
||||
|
||||
You can achieve this by configuring a custom `OidcSessionStrategy`, like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source=java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
public final class MySpringDataOidcSessionStrategy implements OidcSessionStrategy {
|
||||
private final OidcProviderSessionRepository sessions;
|
||||
|
||||
// ...
|
||||
|
||||
@Override
|
||||
public void saveSessionInformation(OidcSessionInformation info) {
|
||||
this.sessions.save(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OidcSessionInformation(String clientSessionId) {
|
||||
return this.sessions.removeByClientSessionId(clientSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<OidcSessionInformation> removeSessionInformation(OidcLogoutToken token) {
|
||||
return token.getSessionId() != null ?
|
||||
this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
|
||||
this.sessions.removeBySubjectAndIssuerAndAudience(...);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source=kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class MySpringDataOidcSessionStrategy: OidcSessionStrategy {
|
||||
val sessions: OidcProviderSessionRepository
|
||||
|
||||
// ...
|
||||
|
||||
@Override
|
||||
fun saveSessionInformation(info: OidcSessionInformation) {
|
||||
this.sessions.save(info)
|
||||
}
|
||||
|
||||
@Override
|
||||
fun removeSessionInformation(clientSessionId: String): OidcSessionInformation {
|
||||
return this.sessions.removeByClientSessionId(clientSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
fun removeSessionInformation(token: OidcLogoutToken): Iterable<OidcSessionInformation> {
|
||||
return token.getSessionId() != null ?
|
||||
this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
|
||||
this.sessions.removeBySubjectAndIssuerAndAudience(...);
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -211,8 +211,6 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class MyCustomSecurityConfiguration {
|
||||
@@ -220,7 +218,7 @@ public class MyCustomSecurityConfiguration {
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/messages/**").access(hasScope("message:read"))
|
||||
.requestMatchers("/messages/**").hasAuthority("SCOPE_message:read")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
@@ -237,8 +235,6 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class MyCustomSecurityConfiguration {
|
||||
@@ -246,7 +242,7 @@ class MyCustomSecurityConfiguration {
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeRequests {
|
||||
authorize("/messages/**", hasScope("message:read"))
|
||||
authorize("/messages/**", hasAuthority("SCOPE_message:read"))
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
@@ -866,8 +862,6 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class DirectlyConfiguredJwkSetUri {
|
||||
@@ -875,8 +869,8 @@ public class DirectlyConfiguredJwkSetUri {
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/contacts/**").access(hasScope("contacts"))
|
||||
.requestMatchers("/messages/**").access(hasScope("messages"))
|
||||
.requestMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
|
||||
.requestMatchers("/messages/**").hasAuthority("SCOPE_messages")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
|
||||
@@ -889,8 +883,6 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class DirectlyConfiguredJwkSetUri {
|
||||
@@ -898,8 +890,8 @@ class DirectlyConfiguredJwkSetUri {
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeRequests {
|
||||
authorize("/contacts/**", hasScope("contacts"))
|
||||
authorize("/messages/**", hasScope("messages"))
|
||||
authorize("/contacts/**", hasAuthority("SCOPE_contacts"))
|
||||
authorize("/messages/**", hasAuthority("SCOPE_messages"))
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
|
||||
@@ -114,8 +114,8 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver
|
||||
("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");
|
||||
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
@@ -131,7 +131,7 @@ Kotlin::
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val customAuthenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")
|
||||
("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")
|
||||
http {
|
||||
authorizeRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
|
||||
@@ -239,8 +239,6 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class MyCustomSecurityConfiguration {
|
||||
@@ -248,7 +246,7 @@ public class MyCustomSecurityConfiguration {
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/messages/**").access(hasScope("message:read"))
|
||||
.requestMatchers("/messages/**").hasAuthority("SCOPE_message:read")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
@@ -265,8 +263,6 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class MyCustomSecurityConfiguration {
|
||||
@@ -274,7 +270,7 @@ class MyCustomSecurityConfiguration {
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeRequests {
|
||||
authorize("/messages/**", hasScope("SCOPE_message:read"))
|
||||
authorize("/messages/**", hasAuthority("SCOPE_message:read"))
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
@@ -551,8 +547,6 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class MappedAuthorities {
|
||||
@@ -560,8 +554,8 @@ public class MappedAuthorities {
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
|
||||
.requestMatchers("/contacts/**").access(hasScope("contacts"))
|
||||
.requestMatchers("/messages/**").access(hasScope("messages"))
|
||||
.requestMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
|
||||
.requestMatchers("/messages/**").hasAuthority("SCOPE_messages")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
|
||||
@@ -574,8 +568,6 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class MappedAuthorities {
|
||||
@@ -583,8 +575,8 @@ class MappedAuthorities {
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeRequests {
|
||||
authorize("/contacts/**", hasScope("contacts"))
|
||||
authorize("/messages/**", hasScope("messages"))
|
||||
authorize("/contacts/**", hasAuthority("SCOPE_contacts"))
|
||||
authorize("/messages/**", hasAuthority("SCOPE_messages"))
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= SecurityMockMvcRequestBuilders
|
||||
== SecurityMockMvcRequestBuilders
|
||||
|
||||
Spring MVC Test also provides a `RequestBuilder` interface that can be used to create the `MockHttpServletRequest` used in your test.
|
||||
Spring Security provides a few `RequestBuilder` implementations that can be used to make testing easier.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= SecurityMockMvcResultHandlers
|
||||
=== SecurityMockMvcResultHandlers
|
||||
|
||||
Spring Security provides a few ``ResultHandler``s implementations.
|
||||
In order to use Spring Security's ``ResultHandler``s implementations ensure the following static import is used:
|
||||
@@ -8,7 +8,7 @@ In order to use Spring Security's ``ResultHandler``s implementations ensure the
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultHandlers.*;
|
||||
----
|
||||
|
||||
== Exporting the SecurityContext
|
||||
==== Exporting the SecurityContext
|
||||
|
||||
Often times we want to query a repository to see if some `MockMvc` request actually persisted in the database.
|
||||
In some cases our repository query uses the xref:features/integrations/data.adoc[Spring Data Integration] to filter the results based on current user's username or any other property.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= SecurityMockMvcResultMatchers
|
||||
== SecurityMockMvcResultMatchers
|
||||
|
||||
At times it is desirable to make various security related assertions about a request.
|
||||
To accommodate this need, Spring Security Test support implements Spring MVC Test's `ResultMatcher` interface.
|
||||
@@ -22,7 +22,7 @@ import org.springframework.security.test.web.servlet.response.SecurityMockMvcRes
|
||||
----
|
||||
======
|
||||
|
||||
== Unauthenticated Assertion
|
||||
=== Unauthenticated Assertion
|
||||
|
||||
At times it may be valuable to assert that there is no authenticated user associated with the result of a `MockMvc` invocation.
|
||||
For example, you might want to test submitting an invalid username and password and verify that no user is authenticated.
|
||||
@@ -49,7 +49,7 @@ mvc
|
||||
----
|
||||
======
|
||||
|
||||
== Authenticated Assertion
|
||||
=== Authenticated Assertion
|
||||
|
||||
It is often times that we must assert that an authenticated user exists.
|
||||
For example, we may want to verify that we authenticated successfully.
|
||||
|
||||
@@ -1,13 +1,43 @@
|
||||
[[new]]
|
||||
= What's New in Spring Security 6.2
|
||||
= What's New in Spring Security 6.1
|
||||
|
||||
Spring Security 6.2 provides a number of new features.
|
||||
Spring Security 6.1 provides a number of new features.
|
||||
Below are the highlights of the release.
|
||||
|
||||
== Configuration
|
||||
== Core
|
||||
|
||||
* https://github.com/spring-projects/spring-security/issues/5011[gh-5011] - xref:servlet/integrations/cors.adoc[(docs)] Automatically enable `.cors()` if `CorsConfigurationSource` bean is present
|
||||
* https://github.com/spring-projects/spring-security/issues/13204[gh-13204] - xref:migration-7/configuration.adoc#_use_with_instead_of_apply_for_custom_dsls[(docs)] Add `AbstractConfiguredSecurityBuilder.with(...)` method to apply configurers returning the builder
|
||||
* https://github.com/spring-projects/spring-security/pull/13587[gh-13587] - https://spring.io/blog/2023/08/22/tackling-the-oauth2-client-component-model-in-spring-security/[blog post] Simplify configuration of OAuth2 Client component model
|
||||
* https://github.com/spring-projects/spring-security/issues/7845[gh-7845] - xref:reactive/oauth2/login/logout.adoc#configure-provider-initiated-oidc-logout[docs] Add OIDC Back-channel Logout Support
|
||||
* https://github.com/spring-projects/spring-security/pull/13857[gh-13857] - xref:servlet/authorization/authorize-http-requests.adoc#match-by-mvc[docs] Add servlet pattern support to AuthorizeHttpRequests
|
||||
* https://github.com/spring-projects/spring-security/issues/12233[gh-12233] - SecuredAuthorizationManager allows customizing underlying AuthorizationManager
|
||||
* https://github.com/spring-projects/spring-security/issues/12231[gh-12231] - Add Authority Collection Authorization Manager
|
||||
|
||||
== OAuth 2.0
|
||||
|
||||
* https://github.com/spring-projects/spring-security/issues/10309[gh-10309] - xref:servlet/oauth2/resource-server/jwt.adoc[(docs)] - Add Nimbus(Reactive)JwtDecoder#withIssuerLocation
|
||||
* https://github.com/spring-projects/spring-security/issues/12907[gh-12907] - Configure principal claim name in ReactiveJwtAuthenticationConverter
|
||||
|
||||
== SAML 2.0
|
||||
|
||||
* https://github.com/spring-projects/spring-security/issues/12604[gh-12604] - Support AuthnRequestSigned metadata attribute
|
||||
* https://github.com/spring-projects/spring-security/issues/12846[gh-12846] - Metadata supports multiple entities and EntitiesDescriptor
|
||||
* https://github.com/spring-projects/spring-security/issues/11828[gh-11828] - xref:servlet/saml2/metadata.adoc[(docs)] - Add saml2Metadata to DSL
|
||||
* https://github.com/spring-projects/spring-security/issues/12843[gh-12843] - xref:servlet/saml2/logout.adoc[(docs)] - Allow Relying Party to be Deduced from LogoutRequest
|
||||
* https://github.com/spring-projects/spring-security/issues/10243[gh-10243] - xref:servlet/saml2/login/authentication.adoc[(docs)] - Allow Relying Party to be Deduced from SAML Response
|
||||
* https://github.com/spring-projects/spring-security/issues/12842[gh-12842] - Add RelyingPartyRegistration placeholder resolution component
|
||||
* https://github.com/spring-projects/spring-security/issues/12845[gh-12845] - Support issuing LogoutResponse after already logged out
|
||||
|
||||
== Observability
|
||||
|
||||
* https://github.com/spring-projects/spring-security/issues/12534[gh-12534] - Customize Authentication and Authorization observation conventions
|
||||
|
||||
== Web
|
||||
|
||||
* https://github.com/spring-projects/spring-security/issues/12751[gh-12751] - Add RequestMatchers factory class
|
||||
* https://github.com/spring-projects/spring-security/issues/12847[gh-12847] - Propagate variables through And and OrRequestMatcher
|
||||
|
||||
== Docs
|
||||
|
||||
In our ongoing efforts to update Spring Security's documentation, several additional sections were fully re-written:
|
||||
|
||||
* https://github.com/spring-projects/spring-security/issues/13088[gh-13088] - xref:servlet/authorization/index.adoc[(docs)] - Revisit Authorization documentation
|
||||
* https://github.com/spring-projects/spring-security/issues/12681[gh-12681] - xref:servlet/authentication/session-management.adoc[(docs)] - Revisit Session Management documentation
|
||||
* https://github.com/spring-projects/spring-security/issues/13062[gh-13062] - xref:servlet/authentication/logout.adoc[(docs)] - Revisit Logout documentation
|
||||
* https://github.com/spring-projects/spring-security/issues/13089[gh-13089] - Revisit CSRF Documentation
|
||||
Reference in New Issue
Block a user