Revert unnecessary merges on 6.0.x

This commit removes unnecessary main-branch merges starting from
8750608b5b and adds the following
needed commit(s) that were made afterward:

- 5dce82c48b
This commit is contained in:
Steve Riesenberg
2023-10-31 15:11:45 -05:00
parent e9d4223402
commit 9db33f33c7
676 changed files with 6306 additions and 43249 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

View File

@@ -2,11 +2,13 @@
* xref:prerequisites.adoc[Prerequisites]
* xref:community.adoc[Community]
* 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]
@@ -47,7 +49,6 @@
*** xref:servlet/authentication/anonymous.adoc[Anonymous]
*** xref:servlet/authentication/preauth.adoc[Pre-Authentication]
*** xref:servlet/authentication/jaas.adoc[JAAS]
*** xref:servlet/authentication/cas.adoc[CAS]
*** xref:servlet/authentication/x509.adoc[X509]
*** xref:servlet/authentication/runas.adoc[Run-As]
*** xref:servlet/authentication/logout.adoc[Logout]
@@ -55,6 +56,9 @@
** xref:servlet/authorization/index.adoc[Authorization]
*** xref:servlet/authorization/architecture.adoc[Authorization Architecture]
*** xref:servlet/authorization/authorize-http-requests.adoc[Authorize HTTP Requests]
*** xref:servlet/authorization/authorize-requests.adoc[Authorize HTTP Requests with FilterSecurityInterceptor]
*** xref:servlet/authorization/expression-based.adoc[Expression-Based Access Control]
*** xref:servlet/authorization/secure-objects.adoc[Secure Object Implementations]
*** xref:servlet/authorization/method-security.adoc[Method Security]
*** xref:servlet/authorization/acls.adoc[Domain Object Security ACLs]
*** xref:servlet/authorization/events.adoc[Authorization Events]

View File

@@ -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.

View File

@@ -73,4 +73,4 @@ interface MessageRepository : PagingAndSortingRepository<Message?, Long?> {
This checks to see if the `Authentication.getPrincipal().getId()` is equal to the recipient of the `Message`.
Note that this example assumes you have customized the principal to be an Object that has an id property.
By exposing the `SecurityEvaluationContextExtension` bean, all of the xref:servlet/authorization/method-security.adoc#authorization-expressions[Common Security Expressions] are available within the Query.
By exposing the `SecurityEvaluationContextExtension` bean, all of the xref:servlet/authorization/expression-based.adoc#common-expressions[Common Security Expressions] are available within the Query.

View File

@@ -1,125 +0,0 @@
= Configuration Migrations
The following steps relate to changes around how to configure `HttpSecurity`, `WebSecurity` and related components.
== Use the Lambda DSL
The Lambda DSL is present in Spring Security since version 5.2, and it allows HTTP security to be configured using lambdas.
You may have seen this style of configuration in the Spring Security documentation or samples.
Let us take a look at how a lambda configuration of HTTP security compares to the previous configuration style.
[source,java]
.Configuration using lambdas
----
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/blog/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(formLogin -> formLogin
.loginPage("/login")
.permitAll()
)
.rememberMe(Customizer.withDefaults());
return http.build();
}
}
----
[source,java]
.Equivalent configuration without using lambdas
----
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests()
.requestMatchers("/blog/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.rememberMe();
return http.build();
}
}
----
The Lambda DSL is the preferred way to configure Spring Security, the prior configuration style will not be valid in Spring Security 7 where the usage of the Lambda DSL will be required.
This has been done mainly for a couple of reasons:
- The previous way it was not clear what object was getting configured without knowing what the return type was.
The deeper the nesting the more confusing it became.
Even experienced users would think that their configuration was doing one thing when in fact, it was doing something else.
- Consistency.
Many code bases switched between the two styles which caused inconsistencies that made understanding the configuration difficult and often led to misconfigurations.
=== Lambda DSL Configuration Tips
When comparing the two samples above, you will notice some key differences:
- In the Lambda DSL there is no need to chain configuration options using the `.and()` method.
The `HttpSecurity` instance is automatically returned for further configuration after the call to the lambda method.
- `Customizer.withDefaults()` enables a security feature using the defaults provided by Spring Security.
This is a shortcut for the lambda expression `it -> {}`.
=== WebFlux Security
You may also configure WebFlux security using lambdas in a similar manner.
Below is an example configuration using lambdas.
[source,java]
.WebFlux configuration using lambdas
----
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/blog/**").permitAll()
.anyExchange().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(formLogin -> formLogin
.loginPage("/login")
);
return http.build();
}
}
----
=== Goals of the Lambda DSL
The Lambda DSL was created to accomplish to following goals:
- Automatic indentation makes the configuration more readable.
- 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].

View File

@@ -1,8 +0,0 @@
[[preparing]]
= Preparing for 7.0
While Spring Security 7.0 does not have a release date yet, it is important to start preparing for it now.
This preparation guide is designed to summarize the biggest changes in Spring Security 7.0 and provide steps to prepare for them.
It is important to keep your application up to date with the latest Spring Security 6 and Spring Boot 3 releases.

View File

@@ -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.

View File

@@ -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].

View File

@@ -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.

View 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.
====

View 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.

View 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, xref:servlet/authorization/authorize-requests.adoc#filtersecurityinterceptor-every-request[`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"`
====

View 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.

View 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.

View File

@@ -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[]

View File

@@ -197,8 +197,8 @@ Java::
----
@Bean
ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations() {
ObservationPredicate predicate = (name, context) -> !name.startsWith("spring.security.");
return (registry) -> registry.observationConfig().observationPredicate(predicate);
ObservationPredicate predicate = (name, context) -> !name.startsWith("spring.security.")
return (registry) -> registry.observationConfig().observationPredicate(predicate)
}
----

View File

@@ -41,7 +41,6 @@ public class OAuth2ClientSecurityConfig {
.clientRegistrationRepository(this.clientRegistrationRepository())
.authorizedClientRepository(this.authorizedClientRepository())
.authorizationRequestRepository(this.authorizationRequestRepository())
.authorizationRequestResolver(this.authorizationRequestResolver())
.authenticationConverter(this.authenticationConverter())
.authenticationManager(this.authenticationManager())
);
@@ -66,7 +65,6 @@ class OAuth2ClientSecurityConfig {
clientRegistrationRepository = clientRegistrationRepository()
authorizedClientRepository = authorizedClientRepository()
authorizationRequestRepository = authorizedRequestRepository()
authorizationRequestResolver = authorizationRequestResolver()
authenticationConverter = authenticationConverter()
authenticationManager = authenticationManager()
}

View File

@@ -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.

View File

@@ -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(...);
}
}
----
======

View File

@@ -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 {
@@ -340,7 +336,6 @@ This is handy when you need deeper configuration, such as <<webflux-oauth2resour
==== Exposing a `ReactiveJwtDecoder` `@Bean`
Alternately, exposing a `ReactiveJwtDecoder` `@Bean` has the same effect as `decoder()`:
You can construct one with a `jwkSetUri` like so:
[tabs]
======
@@ -360,57 +355,7 @@ Kotlin::
----
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build()
}
----
======
or you can use the issuer and have `NimbusReactiveJwtDecoder` look up the `jwkSetUri` when `build()` is invoked, like the following:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build()
}
----
======
Or, if the defaults work for you, you can also use `JwtDecoders`, which does the above in addition to configuring the decoder's validator:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return ReactiveJwtDecoders.fromIssuerLocation(issuer);
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return ReactiveJwtDecoders.fromIssuerLocation(issuer)
return ReactiveJwtDecoders.fromIssuerLocation(issuerUri)
}
----
======
@@ -451,7 +396,7 @@ Java::
----
@Bean
ReactiveJwtDecoder jwtDecoder() {
return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).build();
}
----
@@ -462,7 +407,7 @@ Kotlin::
----
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).build()
}
----
@@ -478,7 +423,7 @@ Java::
----
@Bean
ReactiveJwtDecoder jwtDecoder() {
return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build();
}
----
@@ -489,7 +434,7 @@ Kotlin::
----
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return NimbusReactiveJwtDecoder.withIssuerLocation(this.issuer)
return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build()
}
----
@@ -505,7 +450,7 @@ Java::
----
@Bean
ReactiveJwtDecoder jwtDecoder() {
return NimbusReactiveJwtDecoder.withIssuerLocation(this.jwkSetUri)
return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithms(algorithms -> {
algorithms.add(RS512);
algorithms.add(ES512);
@@ -519,7 +464,7 @@ Kotlin::
----
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return NimbusReactiveJwtDecoder.withIssuerLocation(this.jwkSetUri)
return NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithms {
it.add(RS512)
it.add(ES512)
@@ -686,14 +631,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 +648,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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -163,7 +163,7 @@ Defaults to `true`.
[[nsa-http-use-expressions]]
* **use-expressions**
Enables EL-expressions in the `access` attribute, as described in the chapter on xref:servlet/authorization/authorize-http-requests.adoc#authorization-expressions[expression-based access-control].
Enables EL-expressions in the `access` attribute, as described in the chapter on xref:servlet/authorization/expression-based.adoc#el-access-web[expression-based access-control].
The default value is true.

View File

@@ -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-5.6.rnc[schema file] directly.

View File

@@ -76,27 +76,30 @@ The following listing shows pseudo code of `DelegatingFilterProxy`:
======
Java::
+
[source,java,role="primary"]
[source,java,role="primary",subs="+quotes,+macros"]
----
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
Filter delegate = getFilterBean(someBeanName); // <1>
delegate.doFilter(request, response); // <2>
// Lazily get Filter that was registered as a Spring Bean
// For the example in <<servlet-delegatingfilterproxy-figure>> `delegate` is an instance of __Bean Filter~0~__
Filter delegate = getFilterBean(someBeanName);
// delegate work to the Spring Bean
delegate.doFilter(request, response);
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
[source,kotlin,role="secondary",subs="+quotes,+macros"]
----
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
val delegate: Filter = getFilterBean(someBeanName) // <1>
delegate.doFilter(request, response) // <2>
// Lazily get Filter that was registered as a Spring Bean
// For the example in <<servlet-delegatingfilterproxy-figure>> `delegate` is an instance of __Bean Filter~0~__
val delegate: Filter = getFilterBean(someBeanName)
// delegate work to the Spring Bean
delegate.doFilter(request, response)
}
----
======
<1> Lazily get Filter that was registered as a Spring Bean.
For the example in <<servlet-delegatingfilterproxy-figure>> `delegate` is an instance of __Bean Filter~0~__.
<2> Delegate work to the Spring Bean.
Another benefit of `DelegatingFilterProxy` is that it allows delaying looking up `Filter` bean instances.
This is important because the container needs to register the `Filter` instances before the container can start up.

View File

@@ -1,466 +0,0 @@
[[servlet-cas]]
= CAS Authentication
[[cas-overview]]
== Overview
JA-SIG produces an enterprise-wide single sign on system known as CAS.
Unlike other initiatives, JA-SIG's Central Authentication Service is open source, widely used, simple to understand, platform independent, and supports proxy capabilities.
Spring Security fully supports CAS, and provides an easy migration path from single-application deployments of Spring Security through to multiple-application deployments secured by an enterprise-wide CAS server.
You can learn more about CAS at https://www.apereo.org.
You will also need to visit this site to download the CAS Server files.
[[cas-how-it-works]]
== How CAS Works
Whilst the CAS web site contains documents that detail the architecture of CAS, we present the general overview again here within the context of Spring Security.
Spring Security 3.x supports CAS 3.
At the time of writing, the CAS server was at version 3.4.
Somewhere in your enterprise you will need to setup a CAS server.
The CAS server is simply a standard WAR file, so there isn't anything difficult about setting up your server.
Inside the WAR file you will customise the login and other single sign on pages displayed to users.
When deploying a CAS 3.4 server, you will also need to specify an `AuthenticationHandler` in the `deployerConfigContext.xml` included with CAS.
The `AuthenticationHandler` has a simple method that returns a boolean as to whether a given set of Credentials is valid.
Your `AuthenticationHandler` implementation will need to link into some type of backend authentication repository, such as an LDAP server or database.
CAS itself includes numerous ``AuthenticationHandler``s out of the box to assist with this.
When you download and deploy the server war file, it is set up to successfully authenticate users who enter a password matching their username, which is useful for testing.
Apart from the CAS server itself, the other key players are of course the secure web applications deployed throughout your enterprise.
These web applications are known as "services".
There are three types of services.
Those that authenticate service tickets, those that can obtain proxy tickets, and those that authenticate proxy tickets.
Authenticating a proxy ticket differs because the list of proxies must be validated and often times a proxy ticket can be reused.
[[cas-sequence]]
=== Spring Security and CAS Interaction Sequence
The basic interaction between a web browser, CAS server and a Spring Security-secured service is as follows:
* The web user is browsing the service's public pages.
CAS or Spring Security is not involved.
* The user eventually requests a page that is either secure or one of the beans it uses is secure.
Spring Security's `ExceptionTranslationFilter` will detect the `AccessDeniedException` or `AuthenticationException`.
* Because the user's `Authentication` object (or lack thereof) caused an `AuthenticationException`, the `ExceptionTranslationFilter` will call the configured `AuthenticationEntryPoint`.
If using CAS, this will be the `CasAuthenticationEntryPoint` class.
* The `CasAuthenticationEntryPoint` will redirect the user's browser to the CAS server.
It will also indicate a `service` parameter, which is the callback URL for the Spring Security service (your application).
For example, the URL to which the browser is redirected might be https://my.company.com/cas/login?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas.
* After the user's browser redirects to CAS, they will be prompted for their username and password.
If the user presents a session cookie which indicates they've previously logged on, they will not be prompted to login again (there is an exception to this procedure, which we'll cover later).
CAS will use the `PasswordHandler` (or `AuthenticationHandler` if using CAS 3.0) discussed above to decide whether the username and password is valid.
* Upon successful login, CAS will redirect the user's browser back to the original service.
It will also include a `ticket` parameter, which is an opaque string representing the "service ticket".
Continuing our earlier example, the URL the browser is redirected to might be https://server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZ.
* Back in the service web application, the `CasAuthenticationFilter` is always listening for requests to `/login/cas` (this is configurable, but we'll use the defaults in this introduction).
The processing filter will construct a `UsernamePasswordAuthenticationToken` representing the service ticket.
The principal will be equal to `CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER`, whilst the credentials will be the service ticket opaque value.
This authentication request will then be handed to the configured `AuthenticationManager`.
* The `AuthenticationManager` implementation will be the `ProviderManager`, which is in turn configured with the `CasAuthenticationProvider`.
The `CasAuthenticationProvider` only responds to ``UsernamePasswordAuthenticationToken``s containing the CAS-specific principal (such as `CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER`) and ``CasAuthenticationToken``s (discussed later).
* `CasAuthenticationProvider` will validate the service ticket using a `TicketValidator` implementation.
This will typically be a `Cas20ServiceTicketValidator` which is one of the classes included in the CAS client library.
In the event the application needs to validate proxy tickets, the `Cas20ProxyTicketValidator` is used.
The `TicketValidator` makes an HTTPS request to the CAS server in order to validate the service ticket.
It may also include a proxy callback URL, which is included in this example: https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas&ticket=ST-0-ER94xMJmn6pha35CQRoZ&pgtUrl=https://server3.company.com/webapp/login/cas/proxyreceptor.
* Back on the CAS server, the validation request will be received.
If the presented service ticket matches the service URL the ticket was issued to, CAS will provide an affirmative response in XML indicating the username.
If any proxy was involved in the authentication (discussed below), the list of proxies is also included in the XML response.
* [OPTIONAL] If the request to the CAS validation service included the proxy callback URL (in the `pgtUrl` parameter), CAS will include a `pgtIou` string in the XML response.
This `pgtIou` represents a proxy-granting ticket IOU.
The CAS server will then create its own HTTPS connection back to the `pgtUrl`.
This is to mutually authenticate the CAS server and the claimed service URL.
The HTTPS connection will be used to send a proxy granting ticket to the original web application.
For example, https://server3.company.com/webapp/login/cas/proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH.
* The `Cas20TicketValidator` will parse the XML received from the CAS server.
It will return to the `CasAuthenticationProvider` a `TicketResponse`, which includes the username (mandatory), proxy list (if any were involved), and proxy-granting ticket IOU (if the proxy callback was requested).
* Next `CasAuthenticationProvider` will call a configured `CasProxyDecider`.
The `CasProxyDecider` indicates whether the proxy list in the `TicketResponse` is acceptable to the service.
Several implementations are provided with Spring Security: `RejectProxyTickets`, `AcceptAnyCasProxy` and `NamedCasProxyDecider`.
These names are largely self-explanatory, except `NamedCasProxyDecider` which allows a `List` of trusted proxies to be provided.
* `CasAuthenticationProvider` will next request a `AuthenticationUserDetailsService` to load the `GrantedAuthority` objects that apply to the user contained in the `Assertion`.
* If there were no problems, `CasAuthenticationProvider` constructs a `CasAuthenticationToken` including the details contained in the `TicketResponse` and the ``GrantedAuthority``s.
* Control then returns to `CasAuthenticationFilter`, which places the created `CasAuthenticationToken` in the security context.
* The user's browser is redirected to the original page that caused the `AuthenticationException` (or a custom destination depending on the configuration).
It's good that you're still here!
Let's now look at how this is configured
[[cas-client]]
== Configuration of CAS Client
The web application side of CAS is made easy due to Spring Security.
It is assumed you already know the basics of using Spring Security, so these are not covered again below.
We'll assume a namespace based configuration is being used and add in the CAS beans as required.
Each section builds upon the previous section.
A full CAS sample application can be found in the Spring Security xref:samples.adoc#samples[Samples].
[[cas-st]]
=== Service Ticket Authentication
This section describes how to setup Spring Security to authenticate Service Tickets.
Often times this is all a web application requires.
You will need to add a `ServiceProperties` bean to your application context.
This represents your CAS service:
[source,xml]
----
<bean id="serviceProperties"
class="org.springframework.security.cas.ServiceProperties">
<property name="service"
value="https://localhost:8443/cas-sample/login/cas"/>
<property name="sendRenew" value="false"/>
</bean>
----
The `service` must equal a URL that will be monitored by the `CasAuthenticationFilter`.
The `sendRenew` defaults to false, but should be set to true if your application is particularly sensitive.
What this parameter does is tell the CAS login service that a single sign on login is unacceptable.
Instead, the user will need to re-enter their username and password in order to gain access to the service.
The following beans should be configured to commence the CAS authentication process (assuming you're using a namespace configuration):
[source,xml]
----
<security:http entry-point-ref="casEntryPoint">
...
<security:custom-filter position="CAS_FILTER" ref="casFilter" />
</security:http>
<bean id="casFilter"
class="org.springframework.security.cas.web.CasAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
<bean id="casEntryPoint"
class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
<property name="loginUrl" value="https://localhost:9443/cas/login"/>
<property name="serviceProperties" ref="serviceProperties"/>
</bean>
----
For CAS to operate, the `ExceptionTranslationFilter` must have its `authenticationEntryPoint` property set to the `CasAuthenticationEntryPoint` bean.
This can easily be done using xref:servlet/appendix/namespace/http.adoc#nsa-http-entry-point-ref[entry-point-ref] as is done in the example above.
The `CasAuthenticationEntryPoint` must refer to the `ServiceProperties` bean (discussed above), which provides the URL to the enterprise's CAS login server.
This is where the user's browser will be redirected.
The `CasAuthenticationFilter` has very similar properties to the `UsernamePasswordAuthenticationFilter` (used for form-based logins).
You can use these properties to customize things like behavior for authentication success and failure.
Next you need to add a `CasAuthenticationProvider` and its collaborators:
[source,xml,attrs="-attributes"]
----
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="casAuthenticationProvider" />
</security:authentication-manager>
<bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<property name="authenticationUserDetailsService">
<bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<constructor-arg ref="userService" />
</bean>
</property>
<property name="serviceProperties" ref="serviceProperties" />
<property name="ticketValidator">
<bean class="org.apereo.cas.client.validation.Cas20ServiceTicketValidator">
<constructor-arg index="0" value="https://localhost:9443/cas" />
</bean>
</property>
<property name="key" value="an_id_for_this_auth_provider_only"/>
</bean>
<security:user-service id="userService">
<!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that
NoOpPasswordEncoder should be used.
This is not safe for production, but makes reading
in samples easier.
Normally passwords should be hashed using BCrypt -->
<security:user name="joe" password="{noop}joe" authorities="ROLE_USER" />
...
</security:user-service>
----
The `CasAuthenticationProvider` uses a `UserDetailsService` instance to load the authorities for a user, once they have been authenticated by CAS.
We've shown a simple in-memory setup here.
Note that the `CasAuthenticationProvider` does not actually use the password for authentication, but it does use the authorities.
The beans are all reasonably self-explanatory if you refer back to the <<cas-how-it-works,How CAS Works>> section.
This completes the most basic configuration for CAS.
If you haven't made any mistakes, your web application should happily work within the framework of CAS single sign on.
No other parts of Spring Security need to be concerned about the fact CAS handled authentication.
In the following sections we will discuss some (optional) more advanced configurations.
[[cas-singlelogout]]
=== Single Logout
The CAS protocol supports Single Logout and can be easily added to your Spring Security configuration.
Below are updates to the Spring Security configuration that handle Single Logout
[source,xml]
----
<security:http entry-point-ref="casEntryPoint">
...
<security:logout logout-success-url="/cas-logout.jsp"/>
<security:custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>
<security:custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>
</security:http>
<!-- This filter handles a Single Logout Request from the CAS Server -->
<bean id="singleLogoutFilter" class="org.apereo.cas.client.session.SingleSignOutFilter"/>
<!-- This filter redirects to the CAS Server to signal Single Logout should be performed -->
<bean id="requestSingleLogoutFilter"
class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg value="https://localhost:9443/cas/logout"/>
<constructor-arg>
<bean class=
"org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
</constructor-arg>
<property name="filterProcessesUrl" value="/logout/cas"/>
</bean>
----
The `logout` element logs the user out of the local application, but does not end the session with the CAS server or any other applications that have been logged into.
The `requestSingleLogoutFilter` filter will allow the URL of `/spring_security_cas_logout` to be requested to redirect the application to the configured CAS Server logout URL.
Then the CAS Server will send a Single Logout request to all the services that were signed into.
The `singleLogoutFilter` handles the Single Logout request by looking up the `HttpSession` in a static `Map` and then invalidating it.
It might be confusing why both the `logout` element and the `singleLogoutFilter` are needed.
It is considered best practice to logout locally first since the `SingleSignOutFilter` just stores the `HttpSession` in a static `Map` in order to call invalidate on it.
With the configuration above, the flow of logout would be:
* The user requests `/logout` which would log the user out of the local application and send the user to the logout success page.
* The logout success page, `/cas-logout.jsp`, should instruct the user to click a link pointing to `/logout/cas` in order to logout out of all applications.
* When the user clicks the link, the user is redirected to the CAS single logout URL (https://localhost:9443/cas/logout).
* On the CAS Server side, the CAS single logout URL then submits single logout requests to all the CAS Services.
On the CAS Service side, Apereo's `SingleSignOutFilter` processes the logout request by invalidating the original session.
The next step is to add the following to your web.xml
[source,xml]
----
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>
org.apereo.cas.client.session.SingleSignOutHttpSessionListener
</listener-class>
</listener>
----
When using the SingleSignOutFilter you might encounter some encoding issues.
Therefore it is recommended to add the `CharacterEncodingFilter` to ensure that the character encoding is correct when using the `SingleSignOutFilter`.
Again, refer to Apereo CAS's documentation for details.
The `SingleSignOutHttpSessionListener` ensures that when an `HttpSession` expires, the mapping used for single logout is removed.
[[cas-pt-client]]
=== Authenticating to a Stateless Service with CAS
This section describes how to authenticate to a service using CAS.
In other words, this section discusses how to setup a client that uses a service that authenticates with CAS.
The next section describes how to setup a stateless service to Authenticate using CAS.
[[cas-pt-client-config]]
==== Configuring CAS to Obtain Proxy Granting Tickets
In order to authenticate to a stateless service, the application needs to obtain a proxy granting ticket (PGT).
This section describes how to configure Spring Security to obtain a PGT building upon thencas-st[Service Ticket Authentication] configuration.
The first step is to include a `ProxyGrantingTicketStorage` in your Spring Security configuration.
This is used to store PGT's that are obtained by the `CasAuthenticationFilter` so that they can be used to obtain proxy tickets.
An example configuration is shown below
[source,xml]
----
<!--
NOTE: In a real application you should not use an in memory implementation.
You will also want to ensure to clean up expired tickets by calling
ProxyGrantingTicketStorage.cleanup()
-->
<bean id="pgtStorage" class="org.apereo.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>
----
The next step is to update the `CasAuthenticationProvider` to be able to obtain proxy tickets.
To do this replace the `Cas20ServiceTicketValidator` with a `Cas20ProxyTicketValidator`.
The `proxyCallbackUrl` should be set to a URL that the application will receive PGT's at.
Last, the configuration should also reference the `ProxyGrantingTicketStorage` so it can use a PGT to obtain proxy tickets.
You can find an example of the configuration changes that should be made below.
[source,xml]
----
<bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
<property name="ticketValidator">
<bean class="org.apereo.cas.client.validation.Cas20ProxyTicketValidator">
<constructor-arg value="https://localhost:9443/cas"/>
<property name="proxyCallbackUrl"
value="https://localhost:8443/cas-sample/login/cas/proxyreceptor"/>
<property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
</bean>
</property>
</bean>
----
The last step is to update the `CasAuthenticationFilter` to accept PGT and to store them in the `ProxyGrantingTicketStorage`.
It is important the `proxyReceptorUrl` matches the `proxyCallbackUrl` of the `Cas20ProxyTicketValidator`.
An example configuration is shown below.
[source,xml]
----
<bean id="casFilter"
class="org.springframework.security.cas.web.CasAuthenticationFilter">
...
<property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
<property name="proxyReceptorUrl" value="/login/cas/proxyreceptor"/>
</bean>
----
[[cas-pt-client-sample]]
==== Calling a Stateless Service Using a Proxy Ticket
Now that Spring Security obtains PGTs, you can use them to create proxy tickets which can be used to authenticate to a stateless service.
The CAS xref:samples.adoc#samples[sample application] contains a working example in the `ProxyTicketSampleServlet`.
Example code can be found below:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// NOTE: The CasAuthenticationToken can also be obtained using
// SecurityContextHolder.getContext().getAuthentication()
final CasAuthenticationToken token = (CasAuthenticationToken) request.getUserPrincipal();
// proxyTicket could be reused to make calls to the CAS service even if the
// target url differs
final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl);
// Make a remote call using the proxy ticket
final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8");
String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
...
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
protected fun doGet(request: HttpServletRequest, response: HttpServletResponse?) {
// NOTE: The CasAuthenticationToken can also be obtained using
// SecurityContextHolder.getContext().getAuthentication()
val token = request.userPrincipal as CasAuthenticationToken
// proxyTicket could be reused to make calls to the CAS service even if the
// target url differs
val proxyTicket = token.assertion.principal.getProxyTicketFor(targetUrl)
// Make a remote call using the proxy ticket
val serviceUrl: String = targetUrl + "?ticket=" + URLEncoder.encode(proxyTicket, "UTF-8")
val proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8")
}
----
======
[[cas-pt]]
=== Proxy Ticket Authentication
The `CasAuthenticationProvider` distinguishes between stateful and stateless clients.
A stateful client is considered any that submits to the `filterProcessesUrl` of the `CasAuthenticationFilter`.
A stateless client is any that presents an authentication request to `CasAuthenticationFilter` on a URL other than the `filterProcessesUrl`.
Because remoting protocols have no way of presenting themselves within the context of an `HttpSession`, it isn't possible to rely on the default practice of storing the security context in the session between requests.
Furthermore, because the CAS server invalidates a ticket after it has been validated by the `TicketValidator`, presenting the same proxy ticket on subsequent requests will not work.
One obvious option is to not use CAS at all for remoting protocol clients.
However, this would eliminate many of the desirable features of CAS.
As a middle-ground, the `CasAuthenticationProvider` uses a `StatelessTicketCache`.
This is used solely for stateless clients which use a principal equal to `CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER`.
What happens is the `CasAuthenticationProvider` will store the resulting `CasAuthenticationToken` in the `StatelessTicketCache`, keyed on the proxy ticket.
Accordingly, remoting protocol clients can present the same proxy ticket and the `CasAuthenticationProvider` will not need to contact the CAS server for validation (aside from the first request).
Once authenticated, the proxy ticket could be used for URLs other than the original target service.
This section builds upon the previous sections to accommodate proxy ticket authentication.
The first step is to specify to authenticate all artifacts as shown below.
[source,xml]
----
<bean id="serviceProperties"
class="org.springframework.security.cas.ServiceProperties">
...
<property name="authenticateAllArtifacts" value="true"/>
</bean>
----
The next step is to specify `serviceProperties` and the `authenticationDetailsSource` for the `CasAuthenticationFilter`.
The `serviceProperties` property instructs the `CasAuthenticationFilter` to attempt to authenticate all artifacts instead of only ones present on the `filterProcessesUrl`.
The `ServiceAuthenticationDetailsSource` creates a `ServiceAuthenticationDetails` that ensures the current URL, based upon the `HttpServletRequest`, is used as the service URL when validating the ticket.
The method for generating the service URL can be customized by injecting a custom `AuthenticationDetailsSource` that returns a custom `ServiceAuthenticationDetails`.
[source,xml]
----
<bean id="casFilter"
class="org.springframework.security.cas.web.CasAuthenticationFilter">
...
<property name="serviceProperties" ref="serviceProperties"/>
<property name="authenticationDetailsSource">
<bean class=
"org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource">
<constructor-arg ref="serviceProperties"/>
</bean>
</property>
</bean>
----
You will also need to update the `CasAuthenticationProvider` to handle proxy tickets.
To do this replace the `Cas20ServiceTicketValidator` with a `Cas20ProxyTicketValidator`.
You will need to configure the `statelessTicketCache` and which proxies you want to accept.
You can find an example of the updates required to accept all proxies below.
[source,xml]
----
<bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
<property name="ticketValidator">
<bean class="org.apereo.cas.client.validation.Cas20ProxyTicketValidator">
<constructor-arg value="https://localhost:9443/cas"/>
<property name="acceptAnyProxy" value="true"/>
</bean>
</property>
<property name="statelessTicketCache">
<bean class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache">
<property name="cache">
<bean class="net.sf.ehcache.Cache"
init-method="initialise" destroy-method="dispose">
<constructor-arg value="casTickets"/>
<constructor-arg value="50"/>
<constructor-arg value="true"/>
<constructor-arg value="false"/>
<constructor-arg value="3600"/>
<constructor-arg value="900"/>
</bean>
</property>
</bean>
</property>
</bean>
----

View File

@@ -16,7 +16,6 @@ These sections focus on specific ways you may want to authenticate and point bac
* xref:servlet/authentication/passwords/index.adoc#servlet-authentication-unpwd[Username and Password] - how to authenticate with a username/password
* xref:servlet/oauth2/login/index.adoc#oauth2login[OAuth 2.0 Login] - OAuth 2.0 Log In with OpenID Connect and non-standard OAuth 2.0 Login (i.e. GitHub)
* xref:servlet/saml2/index.adoc#servlet-saml2[SAML 2.0 Login] - SAML 2.0 Log In
* xref:servlet/authentication/cas.adoc#servlet-cas[Central Authentication Server (CAS)] - Central Authentication Server (CAS) Support
* xref:servlet/authentication/rememberme.adoc#servlet-rememberme[Remember Me] - how to remember a user past session expiration
* xref:servlet/authentication/jaas.adoc#servlet-jaas[JAAS Authentication] - authenticate with JAAS
* xref:servlet/authentication/preauth.adoc#servlet-preauth[Pre-Authentication Scenarios] - authenticate with an external mechanism such as https://www.siteminder.com/[SiteMinder] or Java EE security but still use Spring Security for authorization and protection against common exploits.

View File

@@ -1,444 +1,154 @@
[[jc-logout]]
= Handling Logouts
In an application where end users can xref:servlet/authentication/index.adoc[login], they should also be able to logout.
This section covers how to customize the handling of logouts.
By default, Spring Security stands up a `/logout` endpoint, so no additional code is necessary.
The rest of this section covers a number of use cases for you to consider:
* I want to <<logout-java-configuration,understand logout's architecture>>
* I want to <<customizing-logout-uris, customize the logout or logout success URI>>
* I want to know when I need to <<permit-logout-endpoints, explicitly permit the `/logout` endpoint>>
* I want to <<clear-all-site-data, clear cookies, storage, and/or cache>> when the user logs out
* I am using OAuth 2.0 and I want to xref:servlet/oauth2/login/advanced.adoc#oauth2login-advanced-oidc-logout[coordinate logout with an Authorization Server]
* I am using SAML 2.0 and I want to xref:servlet/saml2/logout.adoc[coordinate logout with an Identity Provider]
* I am using CAS and I want to xref:servlet/authentication/cas.adoc#cas-singlelogout[coordinate logout with an Identity Provider]
[[logout-architecture]]
[[logout-java-configuration]]
== Understanding Logout's Architecture
== Logout Java/Kotlin Configuration
When you include {spring-boot-reference-url}using.html#using.build-systems.starters[the `spring-boot-starter-security` dependency] or use the `@EnableWebSecurity` annotation, Spring Security will add its logout support and by default respond both to `GET /logout` and `POST /logout`.
When using the `{security-api-url}org/springframework/security/config/annotation/web/builders/HttpSecurity.html[HttpSecurity]` bean, logout capabilities are automatically applied.
The default is that accessing the URL `/logout` logs the user out by:
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`.
- Invalidating the HTTP Session
- Cleaning up any RememberMe authentication that was configured
- Clearing the `SecurityContextHolder`
- Clearing the `SecurityContextRepository`
- Redirecting to `/login?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.
Similar to configuring login capabilities, however, you also have various options to further customize your logout requirements:
[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.
If you request `POST /logout`, then it will perform the following default operations using a series of {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[``LogoutHandler``]s:
- Invalidate the HTTP session ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`])
- Clear the xref:servlet/authentication/session-management.adoc#use-securitycontextholderstrategy[`SecurityContextHolderStrategy`] ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`])
- Clear the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`])
- Clean up any xref:servlet/authentication/rememberme.adoc[RememberMe authentication] (`TokenRememberMeServices` / `PersistentTokenRememberMeServices`)
- Clear out any saved xref:servlet/exploits/csrf.adoc[CSRF token] ({security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`])
- xref:servlet/authentication/events.adoc[Fire] a `LogoutSuccessEvent` ({security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.html[`LogoutSuccessEventPublishingLogoutHandler`])
Once completed, then it will exercise its default {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[`LogoutSuccessHandler`] which redirects to `/login?logout`.
[[customizing-logout-uris]]
== Customizing Logout URIs
Since the `LogoutFilter` appears before xref:servlet/authorization/authorize-http-requests.adoc[the `AuthorizationFilter`] in xref:servlet/architecture.adoc#servlet-filterchain-figure[the filter chain], it is not necessary by default to explicitly permit the `/logout` endpoint.
Thus, only <<permit-logout-endpoints,custom logout endpoints>> that you create yourself generally require a `permitAll` configuration to be reachable.
For example, if you want to simply change the URI that Spring Security is matching, you can do so in the `logout` DSL in following way:
.Custom Logout Uri
.Logout Configuration
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
.logout((logout) -> logout.logoutUrl("/my/logout/uri"))
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
logout {
logoutUrl = "/my/logout/uri"
}
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.logout(logout -> logout // <1>
.logoutUrl("/my/logout") // <2>
.logoutSuccessUrl("/my/index") // <3>
.logoutSuccessHandler(logoutSuccessHandler) // <4>
.invalidateHttpSession(true) // <5>
.addLogoutHandler(logoutHandler) // <6>
.deleteCookies(cookieNamesToClear) // <7>
)
...
}
----
Xml::
+
[source,xml,role="secondary"]
----
<logout logout-url="/my/logout/uri"/>
----
======
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.
This is because Spring MVC processes your request after Spring Security does.
You can do this using `authorizeHttpRequests` or `<intercept-url>` like so:
.Custom Logout Endpoint
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/my/success/endpoint").permitAll()
// ...
)
.logout((logout) -> logout.logoutSuccessUrl("/my/success/endpoint"))
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
authorizeHttpRequests {
authorize("/my/success/endpoint", permitAll)
}
logout {
logoutSuccessUrl = "/my/success/endpoint"
-----
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
logout { // <1>
logoutUrl = "/my/logout" // <2>
logoutSuccessUrl = "/my/index" // <3>
logoutSuccessHandler = customLogoutSuccessHandler // <4>
invalidateHttpSession = true // <5>
addLogoutHandler(logoutHandler) // <6>
deleteCookies(cookieNamesToClear) // <7>
}
}
// ...
}
----
Xml::
+
[source,xml,role="secondary"]
----
<http>
<filter-url pattern="/my/success/endpoint" access="permitAll"/>
<logout logout-success-url="/my/success/endpoint"/>
</http>
----
-----
======
In this example, you tell the `LogoutFilter` to redirect to `/my/success/endpoint` when it is done.
And, you explicitly permit the `/my/success/endpoint` endpoint in xref:servlet/authorization/authorize-http-requests.adoc[the `AuthorizationFilter`].
Specifying it twice can be cumbersome, though.
If you are using Java configuration, you can instead set the `permitAll` property in the logout DSL like so:
.Permitting Custom Logout Endpoints
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
.authorizeHttpRequests((authorize) -> authorize
// ...
)
.logout((logout) -> logout
.logoutSuccessUrl("/my/success/endpoint")
.permitAll()
)
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
http
authorizeHttpRequests {
// ...
}
logout {
logoutSuccessUrl = "/my/success/endpoint"
permitAll = true
}
----
======
which will add all logout URIs to the permit list for you.
[[add-logout-handler]]
== Adding Clean-up Actions
If you are using Java configuration, you can add clean up actions of your own by calling the `addLogoutHandler` method in the `logout` DSL, like so:
.Custom Logout Handler
[tabs]
======
Java::
+
[source,java,role="primary"]
----
CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
http
.logout((logout) -> logout.addLogoutHandler(cookies))
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
logout {
addLogoutHandler(CookieClearingLogoutHandler("our-custom-cookie"))
}
}
----
======
<1> Provides logout support.
<2> The URL that triggers log out to occur (the default is `/logout`).
If CSRF protection is enabled (the default), the request must also be a POST.
For more information, see {security-api-url}org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.html#logoutUrl-java.lang.String-[`logoutUrl(java.lang.String logoutUrl)`].
<3> The URL to which to redirect after logout has occurred.
The default is `/login?logout`.
For more information, see {security-api-url}org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.html#logoutSuccessUrl-java.lang.String-[`logoutSuccessUrl(java.lang.String logoutSuccessUrl)`].
<4> Let's you specify a custom `LogoutSuccessHandler`.
If this is specified, `logoutSuccessUrl()` is ignored.
For more information, see {security-api-url}org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.html#logoutSuccessHandler-org.springframework.security.web.authentication.logout.LogoutSuccessHandler-[`LogoutSuccessHandler`].
<5> Specify whether to invalidate the `HttpSession` at the time of logout.
This is *true* by default.
Configures the `SecurityContextLogoutHandler` under the covers.
For more information, see {security-api-url}org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.html#invalidateHttpSession-boolean-[`invalidateHttpSession(boolean invalidateHttpSession)`].
<6> Adds a `LogoutHandler`.
By default, `SecurityContextLogoutHandler` is added as the last `LogoutHandler`.
<7> Lets specifying the names of cookies be removed on logout success.
This is a shortcut for adding a `CookieClearingLogoutHandler` explicitly.
[NOTE]
Because {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[``LogoutHandler``]s are for the purposes of cleanup, they should not throw exceptions.
====
Logouts can also be configured by using the XML Namespace notation.
See the documentation for the xref:servlet/appendix/namespace/http.adoc#nsa-logout[ logout element] in the Spring Security XML Namespace section for further details.
====
[TIP]
Since {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[`LogoutHandler`] is a functional interface, you can provide a custom one as a lambda.
Generally, to customize logout functionality, you can add
`{security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[LogoutHandler]`
or
`{security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[LogoutSuccessHandler]`
implementations.
For many common scenarios, these handlers are applied under the
covers when using the fluent API.
Some logout handler configurations are common enough that they are exposed directly in the `logout` DSL and `<logout>` element.
One example is configuring session invalidation and another is which additional cookies should be deleted.
[[ns-logout]]
== Logout XML Configuration
The `logout` element adds support for logging out by navigating to a particular URL.
The default logout URL is `/logout`, but you can set it to something else by setting the `logout-url` attribute.
You can find more information on other available attributes in the namespace appendix.
For example, you can configure the {security-api-url}org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.html[`CookieClearingLogoutHandler`] as seen above.
[[jc-logout-handler]]
== LogoutHandler
[[delete-cookies]]
Or you can instead set the appropriate configuration value like so:
Generally, `{security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[LogoutHandler]`
implementations indicate classes that are able to participate in logout handling.
They are expected to be invoked to perform necessary clean-up.
As a result, they should
not throw exceptions.
Spring Security provides various implementations:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
.logout((logout) -> logout.deleteCookies("our-custom-cookie"))
----
- {security-api-url}org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.html[PersistentTokenBasedRememberMeServices]
- {security-api-url}org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.html[TokenBasedRememberMeServices]
- {security-api-url}org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.html[CookieClearingLogoutHandler]
- {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[CsrfLogoutHandler]
- {security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[SecurityContextLogoutHandler]
- {security-api-url}org/springframework/security/web/authentication/logout/HeaderWriterLogoutHandler.html[HeaderWriterLogoutHandler]
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
logout {
deleteCookies = "our-custom-cookie"
}
}
----
See xref:servlet/authentication/rememberme.adoc#remember-me-impls[Remember-Me Interfaces and Implementations] for details.
Xml::
+
[source,kotlin,role="secondary"]
----
<http>
<logout delete-cookies="our-custom-cookie"/>
</http>
----
======
Instead of providing `LogoutHandler` implementations directly, the fluent API also provides shortcuts that provide the respective `LogoutHandler` implementations under the covers.
For example, `deleteCookies()` lets you specify the names of one or more cookies to be removed on logout success.
This is a shortcut compared to adding a `CookieClearingLogoutHandler`.
[NOTE]
Specifying that the `JSESSIONID` cookie is not necessary since {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] removes it by virtue of invalidating the session.
[[jc-logout-success-handler]]
== LogoutSuccessHandler
[[clear-all-site-data]]
=== Using Clear-Site-Data to Log Out the User
The `LogoutSuccessHandler` is called after a successful logout by the `LogoutFilter`, to handle (for example)
redirection or forwarding to the appropriate destination.
Note that the interface is almost the same as the `LogoutHandler` but may raise an exception.
The `Clear-Site-Data` HTTP header is one that browsers support as an instruction to clear cookies, storage, and cache that belong to the owning website.
This is a handy and secure way to ensure that everything, including the session cookie, is cleaned up on logout.
Spring Security provides the following implementations:
You can add configure Spring Security to write the `Clear-Site-Data` header on logout like so:
- {security-api-url}org/springframework/security/web/authentication/logout/SimpleUrlLogoutSuccessHandler.html[SimpleUrlLogoutSuccessHandler]
- HttpStatusReturningLogoutSuccessHandler
.Using Clear-Site-Data
[tabs]
======
Java::
+
[source,java,role="primary"]
----
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
----
As mentioned earlier, you need not specify the `SimpleUrlLogoutSuccessHandler` directly.
Instead, the fluent API provides a shortcut by setting the `logoutSuccessUrl()`.
This sets up the `SimpleUrlLogoutSuccessHandler` under the covers.
The provided URL is redirected to after a logout has occurred.
The default is `/login?logout`.
Kotlin::
+
[source,kotlin,role="secondary"]
----
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter())
http {
logout {
addLogoutHandler(clearSiteData)
}
}
----
======
You give the `ClearSiteDataHeaderWriter` constructor the list of things that you want to be cleared out.
The above configuration clears out all site data, but you can also configure it to remove just cookies like so:
.Using Clear-Site-Data to Clear Cookies
[tabs]
======
Java::
+
[source,java,role="primary"]
----
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directives.COOKIES));
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directives.COOKIES))
http {
logout {
addLogoutHandler(clearSiteData)
}
}
----
======
[[customizing-logout-success]]
== Customizing Logout Success
While using `logoutSuccessUrl` will suffice for most cases, you may need to do something different from redirecting to a URL once logout is complete.
{security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[`LogoutSuccessHandler`] is the Spring Security component for customizing logout success actions.
For example, instead of redirecting, you may want to only return a status code.
In this case, you can provide a success handler instance, like so:
.Using Clear-Site-Data to Clear Cookies
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
.logout((logout) -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
logout {
logoutSuccessHandler = HttpStatusReturningLogoutSuccessHandler()
}
}
----
Xml::
+
[source,xml,role="secondary"]
----
<bean name="mySuccessHandlerBean" class="org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler"/>
<http>
<logout success-handler-ref="mySuccessHandlerBean"/>
</http>
----
======
[TIP]
Since {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[`LogoutSuccessHandler`] is a functional interface, you can provide a custom one as a lambda.
[[creating-custom-logout-endpoint]]
== Creating a Custom Logout Endpoint
It is strongly recommended that you use the provided `logout` DSL to configure logout.
One reason is that its easy to forget to call the needed Spring Security components to ensure a proper and complete logout.
In fact, it is often simpler to <<add-logout-handler, register a custom `LogoutHandler`>> than create a {spring-framework-reference-url}web.html#spring-web[Spring MVC] endpoint for performing logout.
That said, if you find yourself in a circumstance where a custom logout endpoint is needed, like the following one:
.Custom Logout Endpoint
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@PostMapping("/my/logout")
public String performLogout() {
// .. perform logout
return "redirect:/home";
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@PostMapping("/my/logout")
fun performLogout(): String {
// .. perform logout
return "redirect:/home"
}
----
======
then you will need to have that endpoint invoke Spring Security's {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] to ensure a secure and complete logout.
Something like the following is needed at a minimum:
.Custom Logout Endpoint
[tabs]
======
Java::
+
[source,java,role="primary"]
----
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
@PostMapping("/my/logout")
public String performLogout(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
// .. perform logout
this.logoutHandler.doLogout(request, response, authentication);
return "redirect:/home";
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
val logoutHandler = SecurityContextLogoutHandler()
@PostMapping("/my/logout")
fun performLogout(val authentication: Authentication, val request: HttpServletRequest, val response: HttpServletResponse): String {
// .. perform logout
this.logoutHandler.doLogout(request, response, authentication)
return "redirect:/home"
}
----
======
Such will clear out the {security-api-url}/org/springframework/security/core/context/SecurityContextHolderStrategy.html[`SecurityContextHolderStrategy`] and {security-api-url}/org/springframework/security/web/context/SecurityContextRepository.html[`SecurityContextRepository`] as needed.
Also, you'll need to <<permit-logout-endpoints, explicitly permit the endpoint>>.
[WARNING]
Failing to call {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] means that xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[the `SecurityContext`] could still be available on subsequent requests, meaning that the user is not actually logged out.
[[testing-logout]]
== Testing Logout
Once you have logout configured you can test it using xref:servlet/test/mockmvc/logout.adoc[Spring Security's MockMvc support].
The `HttpStatusReturningLogoutSuccessHandler` can be interesting in REST API type scenarios.
Instead of redirecting to a URL upon the successful logout, this `LogoutSuccessHandler` lets you provide a plain HTTP status code to be returned.
If not configured, a status code 200 is returned by default.
[[jc-logout-references]]
== Further Logout-Related References
- xref:servlet/authentication/session-management.adoc#properly-clearing-authentication[Properly Clearing Authentication When Explicit Save Is Enabled]
- <<ns-logout, Logout Handling>>
- xref:servlet/test/mockmvc/logout.adoc#test-logout[Testing Logout]
- xref:servlet/integrations/servlet-api.adoc#servletapi-logout[HttpServletRequest.logout()]
- xref:servlet/integrations/servlet-api.adoc#servletapi-logout[`HttpServletRequest.logout()`]
- xref:servlet/authentication/rememberme.adoc#remember-me-impls[Remember-Me Interfaces and Implementations]
- xref:servlet/exploits/csrf.adoc#csrf-considerations-logout[Logging Out] in section CSRF Caveats
- Section xref:servlet/authentication/cas.adoc#cas-singlelogout[Single Logout] (CAS protocol)
- Documentation for the xref:servlet/appendix/namespace/http.adoc#nsa-logout[logout element] in the Spring Security XML Namespace section

View File

@@ -15,7 +15,7 @@ The preceding figure builds off our xref:servlet/architecture.adoc#servlet-secur
image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to the resource `/private` for which it is not authorized.
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`.
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-requests.adoc#servlet-authorization-filtersecurityinterceptor[`FilterSecurityInterceptor`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`.
image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates __Start Authentication__.
The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/web/authentication/www/BasicAuthenticationEntryPoint.html[`BasicAuthenticationEntryPoint`], which sends a WWW-Authenticate header.

View File

@@ -16,7 +16,7 @@ The preceding figure builds off our xref:servlet/architecture.adoc#servlet-secur
image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to the resource (`/private`) for which it is not authorized.
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`.
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-requests.adoc#servlet-authorization-filtersecurityinterceptor[`FilterSecurityInterceptor`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`.
image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates __Start Authentication__ and sends a redirect to the login page with the configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`].
In most cases, the `AuthenticationEntryPoint` is an instance of {security-api-url}org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html[`LoginUrlAuthenticationEntryPoint`].

View File

@@ -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`.
@@ -192,7 +192,29 @@ If you are not sure what `securityContextHolderStrategy` is in the above example
=== Properly Clearing an Authentication
If you are using Spring Security's xref:servlet/authentication/logout.adoc[Logout Support] then it handles a lot of stuff for you including clearing and saving the context.
But, let's say you need to manually log users out of your app. In that case, you'll need to make sure you're xref:servlet/authentication/logout.adoc#creating-custom-logout-endpoint[clearing and saving the context properly].
But, let's say you need to manually log users out of your app. In that case, you'll need to make sure you're clearing and saving the context properly.
Now, you might already be familiar with clearing the `SecurityContextHolder` by doing `SecurityContextHolderStrategy#clearContext()`.
That's great, but if your app requires an xref:migration/servlet/session-management.adoc#_require_explicit_saving_of_securitycontextrepository[explicit save of the context], simply clearing it isn't enough.
The reason is that it doesn't remove it from the `SecurityContextRepository`, which means the `SecurityContext` could still be available for the next requests, and we definitely don't want that.
To make sure the authentication is properly cleared and saved, you can invoke {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[the `SecurityContextLogoutHandler`] which does that for us, like so:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
SecurityContextLogoutHandler handler = new SecurityContextLogoutHandler(); <1>
handler.logout(httpServletRequest, httpServletResponse, null); <2>
----
======
<1> Create a new instance of `SecurityContextLogoutHandler`
<2> Call the `logout` method passing in the `HttpServletRequest`, `HttpServletResponse` and a `null` authentication because it is not required for this handler.
It's important to remember that clearing and saving the context is just one piece of the logout process, therefore we recommend having Spring Security take care of it.
[[stateless-authentication]]
=== Configuring Persistence for Stateless Authentication

View File

@@ -209,4 +209,4 @@ Such classes would use `AclService` to retrieve the relevant ACL and then call `
Alternately, you could use our `AclEntryVoter`, `AclEntryAfterInvocationProvider` or `AclEntryAfterInvocationCollectionFilteringProvider` classes.
All of these classes provide a declarative-based approach to evaluating ACL information at runtime, freeing you from needing to write any code.
See the https://github.com/spring-projects/spring-security-samples[sample applications] to learn how to use these classes.
See the https://github.com/spring-projects/spring-security/tree/master/samples[sample applications] to learn how to use these classes.

View File

@@ -21,80 +21,30 @@ String getAuthority();
----
This method is used by an
`AuthorizationManager` instance to obtain a precise `String` representation of the `GrantedAuthority`.
By returning a representation as a `String`, a `GrantedAuthority` can be easily "read" by most `AuthorizationManager` implementations.
If a `GrantedAuthority` cannot be precisely represented as a `String`, the `GrantedAuthority` is considered "complex" and `getAuthority()` must return `null`.
This method lets an
`AccessDecisionManager` instance to obtain a precise `String` representation of the `GrantedAuthority`.
By returning a representation as a `String`, a `GrantedAuthority` can be easily "`read`" by most `AccessDecisionManager` implementations.
If a `GrantedAuthority` cannot be precisely represented as a `String`, the `GrantedAuthority` is considered "`complex`" and `getAuthority()` must return `null`.
An example of a complex `GrantedAuthority` would be an implementation that stores a list of operations and authority thresholds that apply to different customer account numbers.
An example of a "`complex`" `GrantedAuthority` would be an implementation that stores a list of operations and authority thresholds that apply to different customer account numbers.
Representing this complex `GrantedAuthority` as a `String` would be quite difficult. As a result, the `getAuthority()` method should return `null`.
This indicates to any `AuthorizationManager` that it needs to support the specific `GrantedAuthority` implementation to understand its contents.
This indicates to any `AccessDecisionManager` that it needs to support the specific `GrantedAuthority` implementation to understand its contents.
Spring Security includes one concrete `GrantedAuthority` implementation: `SimpleGrantedAuthority`.
This implementation lets any user-specified `String` be converted into a `GrantedAuthority`.
All `AuthenticationProvider` instances included with the security architecture use `SimpleGrantedAuthority` to populate the `Authentication` object.
[[jc-method-security-custom-granted-authority-defaults]]
By default, role-based authorization rules include `ROLE_` as a prefix.
This means that if there is an authorization rule that requires a security context to have a role of "USER", Spring Security will by default look for a `GrantedAuthority#getAuthority` that returns "ROLE_USER".
You can customize this with `GrantedAuthorityDefaults`.
`GrantedAuthorityDefaults` exists to allow customizing the prefix to use for role-based authorization rules.
You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
.Custom MethodSecurityExpressionHandler
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
companion object {
@Bean
fun grantedAuthorityDefaults() : GrantedAuthorityDefaults {
return GrantedAuthorityDefaults("MYPREFIX_");
}
}
----
Xml::
+
[source,xml,role="secondary"]
----
<bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
<constructor-arg value="MYPREFIX_"/>
</bean>
----
======
[TIP]
====
You expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
====
[[authz-pre-invocation]]
== Invocation Handling
== Pre-Invocation Handling
Spring Security provides interceptors that control access to secure objects, such as method invocations or web requests.
A pre-invocation decision on whether the invocation is allowed to proceed is made by `AuthorizationManager` instances.
Also post-invocation decisions on whether a given value may be returned is made by `AuthorizationManager` instances.
A pre-invocation decision on whether the invocation is allowed to proceed is made by the `AccessDecisionManager`.
=== The AuthorizationManager
`AuthorizationManager` supersedes both <<authz-legacy-note,`AccessDecisionManager` and `AccessDecisionVoter`>>.
Applications that customize an `AccessDecisionManager` or `AccessDecisionVoter` are encouraged to <<authz-voter-adaptation,change to using `AuthorizationManager`>>.
``AuthorizationManager``s are called by Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[request-based], xref:servlet/authorization/method-security.adoc[method-based], and xref:servlet/integrations/websocket.adoc[message-based] authorization components and are responsible for making final access control decisions.
``AuthorizationManager``s are called by the xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] and are responsible for making final access control decisions.
The `AuthorizationManager` interface contains two methods:
[source,java]
@@ -143,10 +93,6 @@ Another manager is the `AuthenticatedAuthorizationManager`.
It can be used to differentiate between anonymous, fully-authenticated and remember-me authenticated users.
Many sites allow certain limited access under remember-me authentication, but require a user to confirm their identity by logging in for full access.
[[authz-authorization-managers]]
==== AuthorizationManagers
There are also helpful static factories in `AuthenticationManagers` for composing individual ``AuthenticationManager``s into more sophisticated expressions.
[[authz-custom-authorization-manager]]
==== Custom Authorization Managers
Obviously, you can also implement a custom `AuthorizationManager` and you can put just about any access-control logic you want in it.
@@ -252,20 +198,12 @@ Java::
[source,java,role="primary"]
----
@Bean
static RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
AccessDecisionVoter hierarchyVoter() {
RoleHierarchy hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
"ROLE_STAFF > ROLE_USER\n" +
"ROLE_USER > ROLE_GUEST");
return hierarchy;
}
// and, if using method security also add
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
return new RoleHierarchyVoter(hierarchy);
}
----
@@ -273,6 +211,10 @@ Xml::
+
[source,java,role="secondary"]
----
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
<constructor-arg ref="roleHierarchy" />
</bean>
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy">
@@ -283,12 +225,6 @@ Xml::
</value>
</property>
</bean>
<!-- and, if using method security also add -->
<bean id="methodSecurityExpressionHandler"
class="org.springframework.security.access.expression.method.MethodSecurityExpressionHandler">
<property ref="roleHierarchy"/>
</bean>
----
======

View File

@@ -0,0 +1,194 @@
[[servlet-authorization-filtersecurityinterceptor]]
= Authorize HttpServletRequest with FilterSecurityInterceptor
:figures: servlet/authorization
[NOTE]
====
`FilterSecurityInterceptor` is in the process of being replaced by xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`].
Consider using that instead.
====
This section builds on xref:servlet/architecture.adoc#servlet-architecture[Servlet Architecture and Implementation] by digging deeper into how xref:servlet/authorization/index.adoc#servlet-authorization[authorization] works within Servlet-based applications.
The {security-api-url}org/springframework/security/web/access/intercept/FilterSecurityInterceptor.html[`FilterSecurityInterceptor`] provides xref:servlet/authorization/index.adoc#servlet-authorization[authorization] for `HttpServletRequest` instances.
It is inserted into the xref:servlet/architecture.adoc#servlet-filterchainproxy[FilterChainProxy] as one of the xref:servlet/architecture.adoc#servlet-security-filters[Security Filters].
The following image shows the role of `FilterSecurityInterceptor`:
.Authorize HttpServletRequest
image::{figures}/filtersecurityinterceptor.png[]
image:{icondir}/number_1.png[] The `FilterSecurityInterceptor` obtains an xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] from the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
image:{icondir}/number_2.png[] `FilterSecurityInterceptor` creates a {security-api-url}org/springframework/security/web/FilterInvocation.html[`FilterInvocation`] from the `HttpServletRequest`, `HttpServletResponse`, and `FilterChain` that are passed into the `FilterSecurityInterceptor`.
image:{icondir}/number_3.png[] It passes the `FilterInvocation` to `SecurityMetadataSource` to get the ``ConfigAttribute``s.
image:{icondir}/number_4.png[] It passes the `Authentication`, `FilterInvocation`, and ``ConfigAttribute``s to the `AccessDecisionManager`.
image:{icondir}/number_5.png[] If authorization is denied, an `AccessDeniedException` is thrown.
In this case, the xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] handles the `AccessDeniedException`.
image:{icondir}/number_6.png[] If access is granted, `FilterSecurityInterceptor` continues with the xref:servlet/architecture.adoc#servlet-filters-review[`FilterChain`], which lets the application process normally.
// configuration (xml/java)
By default, Spring Security's authorization requires all requests to be authenticated.
The following listing shows the explicit configuration:
[[servlet-authorize-requests-defaults]]
.Every Request Must be Authenticated
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
);
return http.build();
}
----
XML::
+
[source,xml,role="secondary"]
----
<http>
<!-- ... -->
<intercept-url pattern="/**" access="authenticated"/>
</http>
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
return http.build()
}
----
======
We can configure Spring Security to have different rules by adding more rules in order of precedence:
.Authorize Requests
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize // <1>
.requestMatchers("/resources/**", "/signup", "/about").permitAll() // <2>
.requestMatchers("/admin/**").hasRole("ADMIN") // <3>
.requestMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // <4>
.anyRequest().denyAll() // <5>
);
return http.build();
}
----
XML::
+
[source,xml,role="secondary"]
----
<http> <!--1-->
<!-- ... -->
<!--2-->
<intercept-url pattern="/resources/**" access="permitAll"/>
<intercept-url pattern="/signup" access="permitAll"/>
<intercept-url pattern="/about" access="permitAll"/>
<intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/> <!--3-->
<intercept-url pattern="/db/**" access="hasRole('ADMIN') and hasRole('DBA')"/> <!--4-->
<intercept-url pattern="/**" access="denyAll"/> <!--5-->
</http>
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests { // <1>
authorize("/resources/**", permitAll) // <2>
authorize("/signup", permitAll)
authorize("/about", permitAll)
authorize("/admin/**", hasRole("ADMIN")) // <3>
authorize("/db/**", "hasRole('ADMIN') and hasRole('DBA')") // <4>
authorize(anyRequest, denyAll) // <5>
}
}
return http.build()
}
----
======
<1> There are multiple authorization rules specified.
Each rule is considered in the order they were declared.
<2> We specified multiple URL patterns that any user can access.
Specifically, any user can access a request if the URL starts with "/resources/", equals "/signup", or equals "/about".
<3> Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
You will notice that since we are invoking the `hasRole` method we do not need to specify the "ROLE_" prefix.
<4> Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA".
You will notice that since we are using the `hasRole` expression we do not need to specify the "ROLE_" prefix.
<5> Any URL that has not already been matched on is denied access.
This is a good strategy if you do not want to accidentally forget to update your authorization rules.
[[filtersecurityinterceptor-every-request]]
== Configure FilterSecurityInterceptor with Dispatcher Types
By default, the `FilterSecurityInterceptor` applies to every request.
This means that if a request is dispatched from a request that was already filtered, the `FilterSecurityInterceptor` will perform the same authorization checks on the dispatched request.
In some scenarios, you may not want to apply authorization on some dispatcher types:
.Permit ASYNC and ERROR dispatcher types
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeRequests((authorize) -> authorize
.dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.ERROR).permitAll()
.anyRequest.authenticated()
)
// ...
return http.build();
}
----
XML::
+
[source,xml,role="secondary"]
----
<http auto-config="true">
<intercept-url request-matcher-ref="dispatcherTypeMatcher" access="permitAll" />
<intercept-url pattern="/**" access="authenticated"/>
</http>
<b:bean id="dispatcherTypeMatcher" class="org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher">
<b:constructor-arg value="ASYNC"/>
<b:constructor-arg value="ERROR"/>
</b:bean>
----
======

View File

@@ -0,0 +1,550 @@
[[el-access]]
= Expression-Based Access Control
Spring Security 3.0 introduced the ability to use Spring Expression Language (SpEL) expressions as an authorization mechanism in addition to the existing configuration attributes and access-decision voters.
Expression-based access control is built on the same architecture but lets complicated Boolean logic be encapsulated in a single expression.
== Overview
Spring Security uses SpEL for expression support and you should look at how that works if you are interested in understanding the topic in more depth.
Expressions are evaluated with a "`root object`" as part of the evaluation context.
Spring Security uses specific classes for web and method security as the root object to provide built-in expressions and access to values, such as the current principal.
[[el-common-built-in]]
=== Common Built-In Expressions
The base class for expression root objects is `SecurityExpressionRoot`.
This provides some common expressions that are available in both web and method security:
[[common-expressions]]
.Common built-in expressions
|===
| Expression | Description
| `hasRole(String role)`
| Returns `true` if the current principal has the specified role.
Example: `hasRole('admin')`
By default, if the supplied role does not start with `ROLE_`, it is added.
You can customize this behavior by modifying the `defaultRolePrefix` on `DefaultWebSecurityExpressionHandler`.
| `hasAnyRole(String... roles)`
| Returns `true` if the current principal has any of the supplied roles (given as a comma-separated list of strings).
Example: `hasAnyRole('admin', 'user')`.
By default, if the supplied role does not start with `ROLE_`, it is added.
You can customize this behavior by modifying the `defaultRolePrefix` on `DefaultWebSecurityExpressionHandler`.
| `hasAuthority(String authority)`
| Returns `true` if the current principal has the specified authority.
Example: `hasAuthority('read')`
| `hasAnyAuthority(String... authorities)`
| Returns `true` if the current principal has any of the supplied authorities (given as a comma-separated list of strings).
Example: `hasAnyAuthority('read', 'write')`.
| `principal`
| Allows direct access to the principal object that represents the current user.
| `authentication`
| Allows direct access to the current `Authentication` object obtained from the `SecurityContext`.
| `permitAll`
| Always evaluates to `true`.
| `denyAll`
| Always evaluates to `false`.
| `isAnonymous()`
| Returns `true` if the current principal is an anonymous user.
| `isRememberMe()`
| Returns `true` if the current principal is a remember-me user.
| `isAuthenticated()`
| Returns `true` if the user is not anonymous.
| `isFullyAuthenticated()`
| Returns `true` if the user is not an anonymous and is not a remember-me user.
| `hasPermission(Object target, Object permission)`
| Returns `true` if the user has access to the provided target for the given permission.
Example, `hasPermission(domainObject, 'read')`.
| `hasPermission(Object targetId, String targetType, Object permission)`
| Returns `true` if the user has access to the provided target for the given permission.
Example, `hasPermission(1, 'com.example.domain.Message', 'read')`.
|===
[[el-access-web]]
== Web Security Expressions
To use expressions to secure individual URLs, you first need to set the `use-expressions` attribute in the `<http>` element to `true`.
Spring Security then expects the `access` attributes of the `<intercept-url>` elements to contain SpEL expressions.
Each expression should evaluate to a Boolean, defining whether access should be allowed or not.
The following listing shows an example:
[source,xml]
----
<http>
<intercept-url pattern="/admin*"
access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
...
</http>
----
Here, we have defined that the `admin` area of an application (defined by the URL pattern) should be available only to users who have the granted authority (`admin`) and whose IP address matches a local subnet.
We have already seen the built-in `hasRole` expression in the previous section.
The `hasIpAddress` expression is an additional built-in expression that is specific to web security.
It is defined by the `WebSecurityExpressionRoot` class, an instance of which is used as the expression root object when evaluating web-access expressions.
This object also directly exposed the `HttpServletRequest` object under the name `request` so that you can invoke the request directly in an expression.
If expressions are being used, a `WebExpressionVoter` is added to the `AccessDecisionManager` that is used by the namespace.
So, if you do not use the namespace and want to use expressions, you have to add one of these to your configuration.
[[el-access-web-beans]]
=== Referring to Beans in Web Security Expressions
If you wish to extend the expressions that are available, you can easily refer to any Spring Bean you expose.
For example, you could use the following, assuming you have a Bean with the name of `webSecurity` that contains the following method signature:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
public class WebSecurity {
public boolean check(Authentication authentication, HttpServletRequest request) {
...
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
class WebSecurity {
fun check(authentication: Authentication?, request: HttpServletRequest?): Boolean {
// ...
}
}
----
======
You could then refer to the method as follows:
.Refer to method
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/user/**").access(new WebExpressionAuthorizationManager("@webSecurity.check(authentication,request)"))
...
)
----
XML::
+
[source,xml,role="secondary"]
----
<http>
<intercept-url pattern="/user/**"
access="@webSecurity.check(authentication,request)"/>
...
</http>
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
authorizeRequests {
authorize("/user/**", "@webSecurity.check(authentication,request)")
}
}
----
======
[[el-access-web-path-variables]]
=== Path Variables in Web Security Expressions
At times, it is nice to be able to refer to path variables within a URL.
For example, consider a RESTful application that looks up a user by ID from a URL path in a format of `+/user/{userId}+`.
You can easily refer to the path variable by placing it in the pattern.
For example, you could use the following if you had a Bean with the name of `webSecurity` that contains the following method signature:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
public class WebSecurity {
public boolean checkUserId(Authentication authentication, int id) {
...
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
class WebSecurity {
fun checkUserId(authentication: Authentication?, id: Int): Boolean {
// ...
}
}
----
======
You could then refer to the method as follows:
.Path Variables
[tabs]
======
Java::
+
[source,java,role="primary",attrs="-attributes"]
----
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/user/{userId}/**").access(new WebExpressionAuthorizationManager("@webSecurity.checkUserId(authentication,#userId)"))
...
);
----
XML::
+
[source,xml,role="secondary",attrs="-attributes"]
----
<http>
<intercept-url pattern="/user/{userId}/**"
access="@webSecurity.checkUserId(authentication,#userId)"/>
...
</http>
----
Kotlin::
+
[source,kotlin,role="secondary",attrs="-attributes"]
----
http {
authorizeRequests {
authorize("/user/{userId}/**", "@webSecurity.checkUserId(authentication,#userId)")
}
}
----
======
In this configuration, URLs that match would pass in the path variable (and convert it) into the `checkUserId` method.
For example, if the URL were `/user/123/resource`, the ID passed in would be `123`.
== Method Security Expressions
Method security is a bit more complicated than a simple allow or deny rule.
Spring Security 3.0 introduced some new annotations to allow comprehensive support for the use of expressions.
[[el-pre-post-annotations]]
=== @Pre and @Post Annotations
There are four annotations that support expression attributes to allow pre and post-invocation authorization checks and also to support filtering of submitted collection arguments or return values.
They are `@PreAuthorize`, `@PreFilter`, `@PostAuthorize`, and `@PostFilter`.
Their use is enabled through the `global-method-security` namespace element:
[source,xml]
----
<global-method-security pre-post-annotations="enabled"/>
----
==== Access Control using @PreAuthorize and @PostAuthorize
The most obviously useful annotation is `@PreAuthorize`, which decides whether a method can actually be invoked or not.
The following example (from the {gh-samples-url}/servlet/xml/java/contacts["Contacts" sample application]) uses the `@PreAuthorize` annotation:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@PreAuthorize("hasRole('USER')")
public void create(Contact contact);
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@PreAuthorize("hasRole('USER')")
fun create(contact: Contact?)
----
======
This means that access is allowed only for users with the `ROLE_USER` role.
Obviously, the same thing could easily be achieved by using a traditional configuration and a simple configuration attribute for the required role.
However, consider the following example:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@PreAuthorize("hasPermission(#contact, 'admin')")
public void deletePermission(Contact contact, Sid recipient, Permission permission);
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@PreAuthorize("hasPermission(#contact, 'admin')")
fun deletePermission(contact: Contact?, recipient: Sid?, permission: Permission?)
----
======
Here, we actually use a method argument as part of the expression to decide whether the current user has the `admin` permission for the given contact.
The built-in `hasPermission()` expression is linked into the Spring Security ACL module through the application context, as we <<el-permission-evaluator,see later in this section>>.
You can access any of the method arguments by name as expression variables.
Spring Security can resolve the method arguments in a number of ways.
Spring Security uses `DefaultSecurityParameterNameDiscoverer` to discover the parameter names.
By default, the following options are tried for a method.
* If Spring Security's `@P` annotation is present on a single argument to the method, the value is used.
This is useful for interfaces compiled with a JDK prior to JDK 8 (which do not contain any information about the parameter names).
The following example uses the `@P` annotation:
+
[tabs]
======
Java::
+
[source,java,role="primary"]
----
import org.springframework.security.access.method.P;
...
@PreAuthorize("#c.name == authentication.name")
public void doSomething(@P("c") Contact contact);
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.security.access.method.P
...
@PreAuthorize("#c.name == authentication.name")
fun doSomething(@P("c") contact: Contact?)
----
======
+
Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation.
* If Spring Data's `@Param` annotation is present on at least one parameter for the method, the value is used.
This is useful for interfaces compiled with a JDK prior to JDK 8 which do not contain any information about the parameter names.
The following example uses the `@Param` annotation:
+
[tabs]
======
Java::
+
[source,java,role="primary"]
----
import org.springframework.data.repository.query.Param;
...
@PreAuthorize("#n == authentication.name")
Contact findContactByName(@Param("n") String name);
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.data.repository.query.Param
...
@PreAuthorize("#n == authentication.name")
fun findContactByName(@Param("n") name: String?): Contact?
----
======
+
Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation.
* If JDK 8 was used to compile the source with the `-parameters` argument and Spring 4+ is being used, the standard JDK reflection API is used to discover the parameter names.
This works on both classes and interfaces.
* Finally, if the code was compiled with the debug symbols, the parameter names are discovered by using the debug symbols.
This does not work for interfaces, since they do not have debug information about the parameter names.
For interfaces, annotations or the JDK 8 approach must be used.
.[[el-pre-post-annotations-spel]]
Any SpEL functionality is available within the expression, so you can also access properties on the arguments.
For example, if you wanted a particular method to allow access only to a user whose username matched that of the contact, you could write
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@PreAuthorize("#contact.name == authentication.name")
fun doSomething(contact: Contact?)
----
======
.[[el-pre-post-annotations-post]]
Here, we access another built-in expression, `authentication`, which is the `Authentication` stored in the security context.
You can also access its `principal` property directly, by using the `principal` expression.
The value is often a `UserDetails` instance, so you might use an expression such as `principal.username` or `principal.enabled`.
==== Filtering using @PreFilter and @PostFilter
Spring Security supports filtering of collections, arrays, maps, and streams by using expressions.
This is most commonly performed on the return value of a method.
The following example uses `@PostFilter`:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
public List<Contact> getAll();
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
fun getAll(): List<Contact?>
----
======
When using the `@PostFilter` annotation, Spring Security iterates through the returned collection or map and removes any elements for which the supplied expression is false.
For an array, a new array instance that contains filtered elements is returned.
`filterObject` refers to the current object in the collection.
When a map is used, it refers to the current `Map.Entry` object, which lets you use `filterObject.key` or `filterObject.value` in the expression.
You can also filter before the method call by using `@PreFilter`, though this is a less common requirement.
The syntax is the same. However, if there is more than one argument that is a collection type, you have to select one by name using the `filterTarget` property of this annotation.
Note that filtering is obviously not a substitute for tuning your data retrieval queries.
If you are filtering large collections and removing many of the entries, this is likely to be inefficient.
[[el-method-built-in]]
=== Built-In Expressions
There are some built-in expressions that are specific to method security, which we have already seen in use earlier.
The `filterTarget` and `returnValue` values are simple enough, but the use of the `hasPermission()` expression warrants a closer look.
[[el-permission-evaluator]]
==== The PermissionEvaluator interface
`hasPermission()` expressions are delegated to an instance of `PermissionEvaluator`.
It is intended to bridge between the expression system and Spring Security's ACL system, letting you specify authorization constraints on domain objects, based on abstract permissions.
It has no explicit dependencies on the ACL module, so you could swap that out for an alternative implementation if required.
The interface has two methods:
[source,java]
----
boolean hasPermission(Authentication authentication, Object targetDomainObject,
Object permission);
boolean hasPermission(Authentication authentication, Serializable targetId,
String targetType, Object permission);
----
These methods map directly to the available versions of the expression, with the exception that the first argument (the `Authentication` object) is not supplied.
The first is used in situations where the domain object, to which access is being controlled, is already loaded.
Then the expression returns `true` if the current user has the given permission for that object.
The second version is used in cases where the object is not loaded but its identifier is known.
An abstract "`type`" specifier for the domain object is also required, letting the correct ACL permissions be loaded.
This has traditionally been the Java class of the object but does not have to be, as long as it is consistent with how the permissions are loaded.
To use `hasPermission()` expressions, you have to explicitly configure a `PermissionEvaluator` in your application context.
The following example shows how to do so:
[source,xml]
----
<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>
<bean id="expressionHandler" class=
"org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator" ref="myPermissionEvaluator"/>
</bean>
----
Where `myPermissionEvaluator` is the bean which implements `PermissionEvaluator`.
Usually, this is the implementation from the ACL module, which is called `AclPermissionEvaluator`.
See the {gh-samples-url}/servlet/xml/java/contacts[`Contacts`] sample application configuration for more details.
==== Method Security Meta Annotations
You can make use of meta annotations for method security to make your code more readable.
This is especially convenient if you find that you repeat the same complex expression throughout your code base.
For example, consider the following:
[source,java]
----
@PreAuthorize("#contact.name == authentication.name")
----
Instead of repeating this everywhere, you can create a meta annotation:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
public @interface ContactPermission {}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Retention(AnnotationRetention.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
annotation class ContactPermission
----
======
You can use meta annotations for any of the Spring Security method security annotations.
To remain compliant with the specification, JSR-250 annotations do not support meta annotations.

View File

@@ -2,12 +2,11 @@
= Authorization
:page-section-summary-toc: 1
Having established xref:servlet/authentication/index.adoc[how users will authenticate], you also need to configure your application's authorization rules.
The advanced authorization capabilities within Spring Security represent one of the most compelling reasons for its popularity.
Irrespective of how you choose to authenticate (whether using a Spring Security-provided mechanism and provider or integrating with a container or other non-Spring Security authentication authority), the authorization services can be used within your application in a consistent and simple way.
You should consider attaching authorization rules to xref:servlet/authorization/authorize-http-requests.adoc[request URIs] and xref:servlet/authorization/method-security.adoc[methods] to begin.
Below there is also wealth of detail about xref:servlet/authorization/architecture.adoc[how Spring Security authorization works] and how, having established a basic model, it can be fine-tuned.
In this part, we explore the different `AbstractSecurityInterceptor` implementations, which were introduced in Part I.
We then move on to explore how to fine-tune authorization through the use of domain access control lists.

View File

@@ -0,0 +1,135 @@
[[secure-object-impls]]
= Secure Object Implementations
This section covers how Spring Security handles Secure Object implementations.
[[aop-alliance]]
== AOP Alliance (MethodInvocation) Security Interceptor
Prior to Spring Security 2.0, securing `MethodInvocation` instances needed a lot of boiler plate configuration.
Now the recommended approach for method security is to use xref:servlet/configuration/xml-namespace.adoc#ns-method-security[namespace configuration].
This way, the method security infrastructure beans are configured automatically for you, so you need not know about the implementation classes.
We provide only a quick overview of the classes that are involved here.
Method security is enforced by using a `MethodSecurityInterceptor`, which secures `MethodInvocation` instances.
Depending on the configuration approach, an interceptor may be specific to a single bean or shared between multiple beans.
The interceptor uses a `MethodSecurityMetadataSource` instance to obtain the configuration attributes that apply to a particular method invocation.
`MapBasedMethodSecurityMetadataSource` is used to store configuration attributes keyed by method names (which can be wildcarded) and will be used internally when the attributes are defined in the application context using the `<intercept-methods>` or `<protect-point>` elements.
Other implementations are used to handle annotation-based configuration.
=== Explicit MethodSecurityInterceptor Configuration
You can configure a `MethodSecurityInterceptor` directly in your application context for use with one of Spring AOP's proxying mechanisms:
[source,xml]
----
<bean id="bankManagerSecurity" class=
"org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="securityMetadataSource">
<sec:method-security-metadata-source>
<sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/>
<sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/>
</sec:method-security-metadata-source>
</property>
</bean>
----
[[aspectj]]
== AspectJ (JoinPoint) Security Interceptor
The AspectJ security interceptor is very similar to the AOP Alliance security interceptor discussed in the previous section.
We discuss only the differences in this section.
The AspectJ interceptor is named `AspectJSecurityInterceptor`.
Unlike the AOP Alliance security interceptor, which relies on the Spring application context to weave in the security interceptor through proxying, the `AspectJSecurityInterceptor` is woven in through the AspectJ compiler.
It would not be uncommon to use both types of security interceptors in the same application, with `AspectJSecurityInterceptor` being used for domain object instance security and the AOP Alliance `MethodSecurityInterceptor` being used for services layer security.
We first consider how the `AspectJSecurityInterceptor` is configured in the Spring application context:
[source,xml]
----
<bean id="bankManagerSecurity" class=
"org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="securityMetadataSource">
<sec:method-security-metadata-source>
<sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/>
<sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/>
</sec:method-security-metadata-source>
</property>
</bean>
----
The two interceptors can share the same `securityMetadataSource`, as the `SecurityMetadataSource` works with `java.lang.reflect.Method` instances rather than an AOP library-specific class.
Your access decisions have access to the relevant AOP library-specific invocation (`MethodInvocation` or `JoinPoint`) and can consider a range of additional criteria (such as method arguments) when making access decisions.
Next, you need to define an AspectJ `aspect`, as the following example shows:
[source,java]
----
package org.springframework.security.samples.aspectj;
import org.springframework.security.access.intercept.aspectj.AspectJSecurityInterceptor;
import org.springframework.security.access.intercept.aspectj.AspectJCallback;
import org.springframework.beans.factory.InitializingBean;
public aspect DomainObjectInstanceSecurityAspect implements InitializingBean {
private AspectJSecurityInterceptor securityInterceptor;
pointcut domainObjectInstanceExecution(): target(PersistableEntity)
&& execution(public * *(..)) && !within(DomainObjectInstanceSecurityAspect);
Object around(): domainObjectInstanceExecution() {
if (this.securityInterceptor == null) {
return proceed();
}
AspectJCallback callback = new AspectJCallback() {
public Object proceedWithObject() {
return proceed();
}
};
return this.securityInterceptor.invoke(thisJoinPoint, callback);
}
public AspectJSecurityInterceptor getSecurityInterceptor() {
return securityInterceptor;
}
public void setSecurityInterceptor(AspectJSecurityInterceptor securityInterceptor) {
this.securityInterceptor = securityInterceptor;
}
public void afterPropertiesSet() throws Exception {
if (this.securityInterceptor == null)
throw new IllegalArgumentException("securityInterceptor required");
}
}
}
----
In the preceding example, the security interceptor is applied to every instance of `PersistableEntity`, which is an abstract class not shown (you can use any other class or `pointcut` expression you like).
For those curious, `AspectJCallback` is needed because the `proceed();` statement has special meaning only within an `around()` body.
The `AspectJSecurityInterceptor` calls this anonymous `AspectJCallback` class when it wants the target object to continue.
You need to configure Spring to load the aspect and wire it with the `AspectJSecurityInterceptor`.
The following example shows a bean declaration that achieves this:
[source,xml]
----
<bean id="domainObjectInstanceSecurityAspect"
class="security.samples.aspectj.DomainObjectInstanceSecurityAspect"
factory-method="aspectOf">
<property name="securityInterceptor" ref="bankManagerSecurity"/>
</bean>
----
Now you can create your beans from anywhere within your application, using whatever means you think fit (e.g. `new Person();`), and they have the security interceptor applied.

View File

@@ -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

View File

@@ -137,7 +137,7 @@ The `ROLE_` prefix is a marker that indicates that a simple comparison with the
In other words, a normal role-based check should be used.
Access-control in Spring Security is not limited to the use of simple roles (hence the use of the prefix to differentiate between different types of security attributes).
We see later how the interpretation can vary. The interpretation of the comma-separated values in the `access` attribute depends on the which implementation of the <<ns-access-manager,`AccessDecisionManager`>> is used.
Since Spring Security 3.0, you can also populate the attribute with an xref:servlet/authorization/authorize-http-requests.adoc#authorization-expressions[EL expression].
Since Spring Security 3.0, you can also populate the attribute with an xref:servlet/authorization/expression-based.adoc#el-access[EL expression].
[NOTE]
@@ -369,7 +369,7 @@ If you replace a namespace filter that requires an authentication entry point (t
== Method Security
Since version 2.0, Spring Security has substantial support for adding security to your service layer methods.
It provides support for JSR-250 annotation security as well as the framework's original `@Secured` annotation.
Since version 3.0, you can also make use of xref:servlet/authorization/method-security.adoc#authorizing-with-annotations[expression-based annotations].
Since version 3.0, you can also make use of xref:servlet/authorization/expression-based.adoc#el-access[expression-based annotations].
You can apply security to a single bean (by using the `intercept-methods` element to decorate the bean declaration), or you can secure multiple beans across the entire service layer using the AspectJ style pointcuts.
[[ns-access-manager]]

View File

@@ -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"/>
----

View File

@@ -1,174 +1,71 @@
[[servlet-hello]]
= Hello Spring Security
This section covers the minimum setup for how to use Spring Security with {spring-boot-reference-url}[Spring Boot] and then points you to next steps after that.
This section covers the minimum setup for how to use Spring Security with Spring Boot.
[NOTE]
====
The completed starter application can be found {gh-samples-url}/servlet/spring-boot/java/hello-security[in our samples repository].
For your convenience, you can download a minimal Spring Boot + Spring Security application https://start.spring.io/starter.zip?type=maven-project&language=java&packaging=jar&jvmVersion=1.8&groupId=example&artifactId=hello-security&name=hello-security&description=Hello%20Security&packageName=example.hello-security&dependencies=web,security[prepared by Spring Initializr].
The completed application can be found {gh-samples-url}/servlet/spring-boot/java/hello-security[in our samples repository].
For your convenience, you can download https://start.spring.io/starter.zip?type=maven-project&language=java&packaging=jar&jvmVersion=1.8&groupId=example&artifactId=hello-security&name=hello-security&description=Hello%20Security&packageName=example.hello-security&dependencies=web,security[a minimal Spring Boot + Spring Security application].
====
[[servlet-hello-dependencies]]
== Updating Dependencies
You first need to add Spring Security to your application's classpath; two ways to do this are to xref:getting-spring-security.adoc#getting-maven-boot[use Maven] or xref:getting-spring-security.adoc#getting-gradle-boot[Gradle].
The only step you need to do is update the dependencies by using xref:getting-spring-security.adoc#getting-maven-boot[Maven] or xref:getting-spring-security.adoc#getting-gradle-boot[Gradle].
[[servlet-hello-starting]]
== Starting Hello Spring Security Boot
With Spring Security <<servlet-hello-dependencies,on the classpath>>, you can now {spring-boot-reference-url}#using.running-your-application[run the Spring Boot application].
The following snippet shows some of the output that indicates that Spring Security is enabled in your application:
You can now https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-running-with-the-maven-plugin[run the Spring Boot application] by using the Maven Plugin's `run` goal.
The following example shows how to do so (and the beginning of the output from doing so):
.Running Spring Boot Application
[tabs]
======
Maven::
+
[source,bash,role="primary"]
----
$ ./mvnw spring-boot:run
...
INFO 23689 --- [ restartedMain] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
...
----
Gradle::
+
[source,bash,role="secondary"]
----
$ ./gradlew :bootRun
...
INFO 23689 --- [ restartedMain] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
...
----
Jar::
+
[source,bash,role="secondary"]
----
$ java -jar target/myapplication-0.0.1.jar
...
INFO 23689 --- [ restartedMain] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
...
----
======
Now that you have it running, you might try hitting an endpoint to see what happens.
If you hit an endpoint without credentials like so:
.Querying a Secured Boot Application
[source,bash]
----
$ curl -i http://localhost:8080/some/path
HTTP/1.1 401
$ ./mvn spring-boot:run
...
INFO 23689 --- [ restartedMain] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
...
----
then Spring Security denies access with a `401 Unauthorized`.
[TIP]
If you provide the same URL in a browser, it will redirect to a default login page.
And if you hit an endpoint with credentials (found in the console output) as follows:
.Querying with Credentials
[source,bash]
----
$ curl -i -u user:8e557245-73e2-4286-969a-ff57fe326336 http://localhost:8080/some/path
HTTP/1.1 404
...
----
then Spring Boot will service the request, returning a `404 Not Found` in this case since `/some/path` doesn't exist.
From here, you can:
* Better understand <<hello-expectations,what Spring Boot enables in Spring Security by default>>
* Read about <<security-use-cases,common use cases>> that Spring Security helps with
* Start configuring xref:servlet/authentication/index.adoc[authentication]
[[hello-expectations]]
[[servlet-hello-auto-configuration]]
== Runtime Expectations
== Spring Boot Auto Configuration
The default arrangement of Spring Boot and Spring Security affords the following behaviors at runtime:
// FIXME: Link to relevant portions of documentation
// FIXME: Link to Spring Boot's Security Auto configuration classes
// FIXME: Add links for what user's should do next
* Requires an authenticated user xref:servlet/authorization/authorize-http-requests.adoc[for any endpoint] (including Boot's `/error` endpoint)
* xref:servlet/authentication/passwords/user-details-service.adoc[Registers a default user] with a generated password at startup (the password is logged to the console; in the preceding example, the password is `8e557245-73e2-4286-969a-ff57fe326336`)
* Protects xref:servlet/authentication/passwords/password-encoder.adoc[password storage with BCrypt] as well as others
* Provides form-based xref:servlet/authentication/passwords/form.adoc[login] and xref:servlet/authentication/logout.adoc[logout] flows
* Authenticates xref:servlet/authentication/passwords/form.adoc[form-based login] as well as xref:servlet/authentication/passwords/basic.adoc[HTTP Basic]
* Provides content negotiation; for web requests, redirects to the login page; for service requests, returns a `401 Unauthorized`
* xref:servlet/exploits/csrf.adoc[Mitigates CSRF] attacks
* xref:servlet/authentication/session-management.adoc#ns-session-fixation[Mitigates Session Fixation] attacks
* Writes xref:servlet/exploits/headers.adoc#servlet-headers-hsts[Strict-Transport-Security] to https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security[ensure HTTPS]
* Writes xref:servlet/exploits/headers.adoc#servlet-headers-content-type-options[X-Content-Type-Options] to mitigate https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-content-type-options[sniffing attacks]
* Writes xref:servlet/exploits/headers.adoc#servlet-headers-cache-control[Cache Control headers] that protect authenticated resources
* Writes xref:servlet/exploits/headers.adoc#servlet-headers-frame-options[X-Frame-Options] to mitigate https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-frame-options[Clickjacking]
* Integrates with xref:servlet/integrations/servlet-api.adoc[``HttpServletRequest``'s authentication methods]
* Publishes xref:servlet/authentication/events.adoc[authentication success and failure events]
Spring Boot automatically:
It can be helpful to understand how Spring Boot is coordinating with Spring Security to achieve this.
Taking a look at {spring-boot-api-url}org/springframework/boot/autoconfigure/security/servlet/SecurityAutoConfiguration.html[Boot's security auto configuration], it does the following (simplified for illustration):
* Enables Spring Security's default configuration, which creates a servlet `Filter` as a bean named `springSecurityFilterChain`.
This bean is responsible for all the security (protecting the application URLs, validating submitted username and passwords, redirecting to the login form, and so on) within your application.
* Creates a `UserDetailsService` bean with a username of `user` and a randomly generated password that is logged to the console.
* Registers the `Filter` with a bean named `springSecurityFilterChain` with the Servlet container for every request.
.Spring Boot Security Auto Configuration
[source,java]
----
@EnableWebSecurity <1>
@Configuration
public class DefaultSecurityConfig {
@Bean
@ConditionalOnMissingBean(UserDetailsService.class)
InMemoryUserDetailsManager inMemoryUserDetailsManager() { <2>
String generatedPassword = // ...;
return new InMemoryUserDetailsManager(User.withUsername("user")
.password(generatedPassword).roles("ROLE_USER").build());
}
Spring Boot is not configuring much, but it does a lot.
A summary of the features follows:
@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher(ApplicationEventPublisher delegate) { <3>
return new DefaultAuthenticationEventPublisher(delegate);
}
}
----
1. Adds the `@EnableWebSecurity` annotation. (Among other things, this publishes xref:servlet/architecture.adoc#servlet-securityfilterchain[Spring Security's default `Filter` chain] as a `@Bean`)
2. Publishes a xref:servlet/authentication/passwords/user-details-service.adoc[`UserDetailsService`] `@Bean` with a username of `user` and a randomly generated password that is logged to the console
3. Publishes an xref:servlet/authentication/events.adoc[`AuthenticationEventPublisher`] `@Bean` for publishing authentication events
* Require an authenticated user for any interaction with the application
* Generate a default login form for you
* Let the user with a username of `user` and a password that is logged to the console to authenticate with form-based authentication (in the preceding example, the password is `8e557245-73e2-4286-969a-ff57fe326336`)
* Protects the password storage with BCrypt
* Lets the user log out
* https://en.wikipedia.org/wiki/Cross-site_request_forgery[CSRF attack] prevention
* https://en.wikipedia.org/wiki/Session_fixation[Session Fixation] protection
* Security Header integration
** https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security[HTTP Strict Transport Security] for secure requests
** https://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx[X-Content-Type-Options] integration
** Cache Control (can be overridden later by your application to allow caching of your static resources)
** X-Frame-Options integration to help prevent https://en.wikipedia.org/wiki/Clickjacking[Clickjacking]
* Integrate with the following Servlet API methods:
** https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getRemoteUser()[`HttpServletRequest#getRemoteUser()`]
** https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal()[`HttpServletRequest.html#getUserPrincipal()`]
** https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#isUserInRole(java.lang.String)[`HttpServletRequest.html#isUserInRole(java.lang.String)`]
** https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#login(java.lang.String,%20java.lang.String)[`HttpServletRequest.html#login(java.lang.String, java.lang.String)`]
** https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#logout()[`HttpServletRequest.html#logout()`]
[NOTE]
Spring Boot adds any `Filter` published as a `@Bean` to the application's filter chain.
This means that using `@EnableWebSecurity` in conjunction with Spring Boot automatically registers Spring Security's filter chain for every request.
[[security-use-cases]]
== Security Use Cases
There are a number of places that you may want to go from here.
To figure out what's next for you and your application, consider these common use cases that Spring Security is built to address:
* I am building a REST API, and I need to xref:servlet/oauth2/resource-server/jwt.adoc[authenticate a JWT] or xref:servlet/oauth2/resource-server/opaque-token.adoc[other bearer token]
* I am building a Web Application, API Gateway, or BFF and
** I need to xref:servlet/oauth2/login/core.adoc[login using OAuth 2.0 or OIDC]
** I need to xref:servlet/saml2/login/index.adoc[login using SAML 2.0]
** I need to xref:servlet/authentication/cas.adoc[login using CAS]
* I need to manage
** Users in xref:servlet/authentication/passwords/ldap.adoc[LDAP] or xref:servlet/authentication/passwords/ldap.adoc#_active_directory[Active Directory], with xref:servlet/integrations/data.adoc[Spring Data], or with xref:servlet/authentication/passwords/jdbc.adoc[JDBC]
** xref:servlet/authentication/passwords/storage.adoc[Passwords]
In case none of those match what you are looking for, consider thinking about your application in the following order:
1. *Protocol*: First, consider the protocol your application will use to communicate.
For servlet-based applications, Spring Security supports HTTP as well as xref:servlet/integrations/websocket.adoc[Websockets].
2. *Authentication*: Next, consider how users will xref:servlet/authentication/index.adoc[authenticate] and if that authentication will be stateful or stateless
3. *Authorization*: Then, consider how you will determine xref:servlet/authorization/index.adoc[what a user is authorized to do]
4. *Defense*: Finally, xref:servlet/exploits/csrf.adoc#csrf-considerations[integrate with Spring Security's default protections] and consider xref:servlet/exploits/headers.adoc[which additional protections you need]

View File

@@ -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
}
----
======

View File

@@ -71,4 +71,4 @@ interface MessageRepository : PagingAndSortingRepository<Message,Long> {
This checks to see if the `Authentication.getPrincipal().getId()` is equal to the recipient of the `Message`.
Note that this example assumes you have customized the principal to be an Object that has an id property.
By exposing the `SecurityEvaluationContextExtension` bean, all of the xref:servlet/authorization/method-security.adoc#authorization-expressions[Common Security Expressions] are available within the Query.
By exposing the `SecurityEvaluationContextExtension` bean, all of the xref:servlet/authorization/expression-based.adoc#common-expressions[Common Security Expressions] are available within the Query.

View File

@@ -21,7 +21,7 @@ In Spring Security 3.0, it can be used in two ways.
The legacy options from Spring Security 2.0 are also supported, but discouraged.
====
The first approach uses a xref:servlet/authorization/authorize-http-requests.adoc#authorization-expressions[web-security expression], which is specified in the `access` attribute of the tag.
The first approach uses a xref:servlet/authorization/expression-based.adoc#el-access-web[web-security expression], which is specified in the `access` attribute of the tag.
The expression evaluation is delegated to the `SecurityExpressionHandler<FilterInvocation>` defined in the application context (you should have web expressions enabled in your `<http>` namespace configuration to make sure this service is available).
So, for example, you might have:

View File

@@ -203,7 +203,7 @@ Java::
@Bean
ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations() {
ObservationPredicate predicate = (name, context) -> !name.startsWith("spring.security.");
return (registry) -> registry.observationConfig().observationPredicate(predicate);
return (registry) -> registry.observationConfig().observationPredicate(predicate)
}
----

View File

@@ -27,7 +27,7 @@ Spring Security 4.0 has introduced authorization support for WebSockets through
In Spring Security 5.8, this support has been refreshed to use the `AuthorizationManager` API.
To configure authorization using Java Configuration, simply include the `@EnableWebSocketSecurity` annotation and publish an `AuthorizationManager<Message<?>>` bean or in xref:servlet/appendix/namespace/websocket.adoc#nsa-websocket-security[XML] use the `use-authorization-manager` attribute.
To configure authorization using Java Configuration, simply include the `@EnableWebSocketSecurity` annotation and publish an `AuthorizationManager<Message<?>>` bean or in XML use the `use-authorization-manager` attribute.
One way to do this is by using the `AuthorizationManagerMessageMatcherRegistry` to specify endpoint patterns like so:
[tabs]
@@ -43,7 +43,7 @@ public class WebSocketSecurityConfig {
@Bean
AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
.simpDestMatchers("/user/**").hasRole("USER") // <3>
.simpDestMatchers("/user/**").authenticated() // <3>
return messages.build();
}
@@ -58,25 +58,33 @@ Kotlin::
@EnableWebSocketSecurity // <1> <2>
open class WebSocketSecurityConfig { // <1> <2>
@Bean
fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
messages.simpDestMatchers("/user/**").hasRole("USER") // <3>
fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
messages.simpDestMatchers("/user/**").authenticated() // <3>
return messages.build()
}
}
----
Xml::
+
[source,xml,role="secondary"]
----
<websocket-message-broker use-authorization-manager="true"> <1> <2>
<intercept-message pattern="/user/**" access="hasRole('USER')"/> <3>
</websocket-message-broker>
----
======
<1> Any inbound CONNECT message requires a valid CSRF token to enforce the <<websocket-sameorigin,Same Origin Policy>>.
<2> The `SecurityContextHolder` is populated with the user within the `simpUser` header attribute for any inbound request.
<3> Our messages require the proper authorization. Specifically, any inbound message that starts with `/user/` will require `ROLE_USER`. You can find additional details on authorization in <<websocket-authorization>>
<3> Our messages require the proper authorization. Specifically, any inbound message that starts with `/user/` will requires `ROLE_USER`. You can find additional details on authorization in <<websocket-authorization>>
Spring Security also provides xref:servlet/appendix/namespace/websocket.adoc#nsa-websocket-security[XML Namespace] support for securing WebSockets.
A comparable XML based configuration looks like the following:
[source,xml]
----
<websocket-message-broker use-authorization-manager="true"> <!--1--> <!--2-->
<intercept-message pattern="/user/**" access="authenticated"/> <!--3-->
</websocket-message-broker>
----
This will ensure that:
<1> Any inbound CONNECT message requires a valid CSRF token to enforce <<websocket-sameorigin,Same Origin Policy>>
<2> The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
<3> Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in <<websocket-authorization>>
=== Custom Authorization
@@ -108,7 +116,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 +164,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 +402,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

View File

@@ -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.
====

View File

@@ -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(...);
}
}
----
======

View File

@@ -27,7 +27,7 @@ The figure above builds off our xref:servlet/architecture.adoc#servlet-securityf
image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to the `/private` resource for which the user is not authorized.
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is _Denied_ by throwing an `AccessDeniedException`.
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-requests.adoc#servlet-authorization-filtersecurityinterceptor[`FilterSecurityInterceptor`] indicates that the unauthenticated request is _Denied_ by throwing an `AccessDeniedException`.
image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates _Start Authentication_.
The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationEntryPoint.html[`BearerTokenAuthenticationEntryPoint`], which sends a `WWW-Authenticate` header.

View File

@@ -176,7 +176,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
----
@@ -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 {
@@ -479,8 +475,7 @@ This is handy when deeper configuration, like <<oauth2resourceserver-jwt-validat
[[oauth2resourceserver-jwt-decoder-bean]]
=== Exposing a `JwtDecoder` `@Bean`
Or, exposing a <<oauth2resourceserver-jwt-architecture-jwtdecoder,`JwtDecoder`>> `@Bean` has the same effect as `decoder()`.
You can construct one with a `jwkSetUri` like so:
Or, exposing a <<oauth2resourceserver-jwt-architecture-jwtdecoder,`JwtDecoder`>> `@Bean` has the same effect as `decoder()`:
[tabs]
======
@@ -505,56 +500,6 @@ fun jwtDecoder(): JwtDecoder {
----
======
or you can use the issuer and have `NimbusJwtDecoder` look up the `jwkSetUri` when `build()` is invoked, like the following:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withIssuerLocation(issuer).build();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withIssuerLocation(issuer).build()
}
----
======
Or, if the defaults work for you, you can also use `JwtDecoders`, which does the above in addition to configuring the decoder's validator:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public JwtDecoders jwtDecoder() {
return JwtDecoders.fromIssuerLocation(issuer);
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): JwtDecoders {
return JwtDecoders.fromIssuerLocation(issuer)
}
----
======
[[oauth2resourceserver-jwt-decoder-algorithm]]
== Configuring Trusted Algorithms
@@ -591,7 +536,7 @@ Java::
----
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).build();
}
----
@@ -602,7 +547,7 @@ Kotlin::
----
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).build()
}
----
@@ -618,7 +563,7 @@ Java::
----
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build();
}
----
@@ -629,7 +574,7 @@ Kotlin::
----
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build()
}
----
@@ -645,7 +590,7 @@ Java::
----
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithms(algorithms -> {
algorithms.add(RS512);
algorithms.add(ES512);
@@ -659,7 +604,7 @@ Kotlin::
----
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withIssuerLocation(this.issuer)
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithms {
it.add(RS512)
it.add(ES512)
@@ -866,8 +811,6 @@ Java::
+
[source,java,role="primary"]
----
import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
@Configuration
@EnableWebSecurity
public class DirectlyConfiguredJwkSetUri {
@@ -875,8 +818,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 +832,6 @@ Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
@Configuration
@EnableWebSecurity
class DirectlyConfiguredJwkSetUri {
@@ -898,8 +839,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 {
@@ -1373,7 +1314,7 @@ Java::
----
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.withDefaults(Collections.singletonMap("sub", this::lookupUserIdBySub));
@@ -1389,7 +1330,7 @@ Kotlin::
----
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build()
val jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build()
val converter = MappedJwtClaimSetConverter
.withDefaults(mapOf("sub" to this::lookupUserIdBySub))
@@ -1497,7 +1438,7 @@ Java::
----
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
jwtDecoder.setClaimSetConverter(new UsernameSubClaimAdapter());
return jwtDecoder;
}
@@ -1509,7 +1450,7 @@ Kotlin::
----
@Bean
fun jwtDecoder(): JwtDecoder {
val jwtDecoder: NimbusJwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build()
val jwtDecoder: NimbusJwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build()
jwtDecoder.setClaimSetConverter(UsernameSubClaimAdapter())
return jwtDecoder
}
@@ -1539,7 +1480,7 @@ public JwtDecoder jwtDecoder(RestTemplateBuilder builder) {
.setReadTimeout(Duration.ofSeconds(60))
.build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).restOperations(rest).build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).restOperations(rest).build();
return jwtDecoder;
}
----
@@ -1554,7 +1495,7 @@ fun jwtDecoder(builder: RestTemplateBuilder): JwtDecoder {
.setConnectTimeout(Duration.ofSeconds(60))
.setReadTimeout(Duration.ofSeconds(60))
.build()
return NimbusJwtDecoder.withIssuerLocation(issuer).restOperations(rest).build()
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).restOperations(rest).build()
}
----
======
@@ -1572,7 +1513,7 @@ Java::
----
@Bean
public JwtDecoder jwtDecoder(CacheManager cacheManager) {
return NimbusJwtDecoder.withIssuerLocation(issuer)
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.cache(cacheManager.getCache("jwks"))
.build();
}
@@ -1584,7 +1525,7 @@ Kotlin::
----
@Bean
fun jwtDecoder(cacheManager: CacheManager): JwtDecoder {
return NimbusJwtDecoder.withIssuerLocation(issuer)
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.cache(cacheManager.getCache("jwks"))
.build()
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -13,11 +13,38 @@ You can configure this in a number of ways including:
To configure these, you'll use the `saml2Login#authenticationManager` method in the DSL.
[[saml2-response-processing-endpoint]]
== Changing the SAML Response Processing Endpoint
[[relyingpartyregistrationresolver-apply]]
== Changing `RelyingPartyRegistration` Lookup
The default endpoint is `+/login/saml2/sso/{registrationId}+`.
You can change this in the DSL and in the associated metadata like so:
`RelyingPartyRegistration` lookup is customized xref:servlet/saml2/login/overview.adoc#servlet-saml2login-rpr-relyingpartyregistrationresolver[in a `RelyingPartyRegistrationResolver`].
To apply a `RelyingPartyRegistrationResolver` when processing `<saml2:Response>` payloads, you should first publish a `Saml2AuthenticationTokenConverter` bean like so:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
Saml2AuthenticationTokenConverter authenticationConverter(InMemoryRelyingPartyRegistrationRepository registrations) {
return new Saml2AuthenticationTokenConverter(new MyRelyingPartyRegistrationResolver(registrations));
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun authenticationConverter(val registrations: InMemoryRelyingPartyRegistrationRepository): Saml2AuthenticationTokenConverter {
return Saml2AuthenticationTokenConverter(MyRelyingPartyRegistrationResolver(registrations));
}
----
======
Recall that the Assertion Consumer Service URL is `+/saml2/login/sso/{registrationId}+` by default.
If you are no longer wanting the `registrationId` in the URL, change it in the filter chain and in your relying party metadata:
[tabs]
======
@@ -63,59 +90,14 @@ Java::
+
[source,java,role="primary"]
----
relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml/SSO")
relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml2/login/sso")
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml/SSO")
----
======
[[relyingpartyregistrationresolver-apply]]
== Changing `RelyingPartyRegistration` lookup
By default, this converter will match against any associated `<saml2:AuthnRequest>` or any `registrationId` it finds in the URL.
Or, if it cannot find one in either of those cases, then it attempts to look it up by the `<saml2:Response#Issuer>` element.
There are a number of circumstances where you might need something more sophisticated, like if you are supporting `ARTIFACT` binding.
In those cases, you can customize lookup through a custom `AuthenticationConverter`, which you can customize like so:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
SecurityFilterChain securityFilters(HttpSecurity http, AuthenticationConverter authenticationConverter) throws Exception {
http
// ...
.saml2Login((saml2) -> saml2.authenticationConverter(authenticationConverter))
// ...
return http.build();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun securityFilters(val http: HttpSecurity, val converter: AuthenticationConverter): SecurityFilterChain {
http {
// ...
.saml2Login {
authenticationConverter = converter
}
// ...
}
return http.build()
}
relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml2/login/sso")
----
======

View File

@@ -16,7 +16,7 @@ The figure above builds off our xref:servlet/architecture.adoc#servlet-securityf
image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to the `/private` resource, for which it is not authorized.
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is _Denied_ by throwing an `AccessDeniedException`.
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-requests.adoc#servlet-authorization-filtersecurityinterceptor[`FilterSecurityInterceptor`] indicates that the unauthenticated request is _Denied_ by throwing an `AccessDeniedException`.
image:{icondir}/number_3.png[] Since the user lacks authorization, the xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates _Start Authentication_.
The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html[`LoginUrlAuthenticationEntryPoint`], which redirects to <<servlet-saml2login-sp-initiated-factory,the `<saml2:AuthnRequest>` generating endpoint>>, `Saml2WebSsoAuthenticationRequestFilter`.
@@ -690,9 +690,9 @@ In a deployed application, that translates to:
The prevailing URI patterns are as follows:
* `+/saml2/authenticate/{registrationId}+` - The endpoint that xref:servlet/saml2/login/authentication-requests.adoc[generates a `<saml2:AuthnRequest>`] based on the configurations for that `RelyingPartyRegistration` and sends it to the asserting party
* `+/login/saml2/sso/+` - The endpoint that xref:servlet/saml2/login/authentication.adoc[authenticates an asserting party's `<saml2:Response>`]; the `RelyingPartyRegistration` is looked up from previously authenticated state or the response's issuer if needed; also supports `+/login/saml2/sso/{registrationId}+`
* `+/logout/saml2/sso+` - The endpoint that xref:servlet/saml2/logout.adoc[processes `<saml2:LogoutRequest>` and `<saml2:LogoutResponse>` payloads]; the `RelyingPartyRegistration` is looked up from previously authenticated state or the request's issuer if needed; also supports `+/logout/saml2/slo/{registrationId}+`
* `+/saml2/metadata+` - The xref:servlet/saml2/metadata.adoc[relying party metadata] for the set of ``RelyingPartyRegistration``s; also supports `+/saml2/metadata/{registrationId}+` or `+/saml2/service-provider-metadata/{registrationId}+` for a specific `RelyingPartyRegistration`
* `+/saml2/login/sso/{registrationId}+` - The endpoint that xref:servlet/saml2/login/authentication.adoc[authenticates an asserting party's `<saml2:Response>`] based on the configurations for that `RelyingPartyRegistration`
* `+/saml2/logout/sso+` - The endpoint that xref:servlet/saml2/logout.adoc[processes `<saml2:LogoutRequest>` and `<saml2:LogoutResponse>` payloads]; the `RelyingPartyRegistration` is looked up from previously authenticated state
* `+/saml2/saml2-service-provider/metadata/{registrationId}+` - The xref:servlet/saml2/metadata.adoc[relying party metadata] for that `RelyingPartyRegistration`
Since the `registrationId` is the primary identifier for a `RelyingPartyRegistration`, it is needed in the URL for unauthenticated scenarios.
If you wish to remove the `registrationId` from the URL for any reason, you can <<servlet-saml2login-rpr-relyingpartyregistrationresolver,specify a `RelyingPartyRegistrationResolver`>> to tell Spring Security how to look up the `registrationId`.
@@ -880,18 +880,128 @@ open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
As seen so far, Spring Security resolves the `RelyingPartyRegistration` by looking for the registration id in the URI path.
Depending on the use case, a number of other strategies are also employed to derive one.
For example:
There are a number of reasons you may want to customize that. Among them:
* For processing `<saml2:Response>`s, the `RelyingPartyRegistration` is looked up from the associated `<saml2:AuthRequest>` or from the `<saml2:Response#Issuer>` element
* For processing `<saml2:LogoutRequest>`s, the `RelyingPartyRegistration` is looked up from the currently logged in user or from the `<saml2:LogoutRequest#Issuer>` element
* For publishing metadata, the `RelyingPartyRegistration`s are looked up from any repository that also implements `Iterable<RelyingPartyRegistration>`
* You may already <<relyingpartyregistrationresolver-single, know which `RelyingPartyRegistration` you need>>
* You may be <<relyingpartyregistrationresolver-entityid, federating many asserting parties>>
When this needs adjustment, you can turn to the specific components for each of these endpoints targeted at customizing this:
To customize the way that a `RelyingPartyRegistration` is resolved, you can configure a custom `RelyingPartyRegistrationResolver`.
The default looks up the registration id from the URI's last path element and looks it up in your `RelyingPartyRegistrationRepository`.
* For SAML Responses, customize the `AuthenticationConverter`
* For Logout Requests, customize the `Saml2LogoutRequestValidatorParametersResolver`
* For Metadata, customize the `Saml2MetadataResponseResolver`
[NOTE]
Remember that if you have any placeholders in your `RelyingPartyRegistration`, your resolver implementation should resolve them.
[[relyingpartyregistrationresolver-single]]
==== Resolving to a Single Consistent `RelyingPartyRegistration`
You can provide a resolver that, for example, always returns the same `RelyingPartyRegistration`:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
public class SingleRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver {
private final RelyingPartyRegistrationResolver delegate;
public SingleRelyingPartyRegistrationResolver(RelyingPartyRegistrationRepository registrations) {
this.delegate = new DefaultRelyingPartyRegistrationResolver(registrations);
}
@Override
public RelyingPartyRegistration resolve(HttpServletRequest request, String registrationId) {
return this.delegate.resolve(request, "single");
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
class SingleRelyingPartyRegistrationResolver(delegate: RelyingPartyRegistrationResolver) : RelyingPartyRegistrationResolver {
override fun resolve(request: HttpServletRequest?, registrationId: String?): RelyingPartyRegistration? {
return this.delegate.resolve(request, "single")
}
}
----
======
[TIP]
You might next take a look at how to use this resolver to customize xref:servlet/saml2/metadata.adoc#servlet-saml2login-metadata[`<saml2:SPSSODescriptor>` metadata production].
[[relyingpartyregistrationresolver-entityid]]
==== Resolving Based on the `<saml2:Response#Issuer>`
When you have one relying party that can accept assertions from multiple asserting parties, you will have as many ``RelyingPartyRegistration``s as asserting parties, with the <<servlet-saml2login-rpr-duplicated, relying party information duplicated across each instance>>.
This carries the implication that the assertion consumer service endpoint will be different for each asserting party, which may not be desirable.
You can instead resolve the `registrationId` via the `Issuer`.
A custom implementation of `RelyingPartyRegistrationResolver` that does this may look like:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
public class SamlResponseIssuerRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver {
private final InMemoryRelyingPartyRegistrationRepository registrations;
// ... constructor
@Override
RelyingPartyRegistration resolve(HttpServletRequest request, String registrationId) {
if (registrationId != null) {
return this.registrations.findByRegistrationId(registrationId);
}
String entityId = resolveEntityIdFromSamlResponse(request);
for (RelyingPartyRegistration registration : this.registrations) {
if (registration.getAssertingPartyDetails().getEntityId().equals(entityId)) {
return registration;
}
}
return null;
}
private String resolveEntityIdFromSamlResponse(HttpServletRequest request) {
// ...
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
class SamlResponseIssuerRelyingPartyRegistrationResolver(val registrations: InMemoryRelyingPartyRegistrationRepository):
RelyingPartyRegistrationResolver {
@Override
fun resolve(val request: HttpServletRequest, val registrationId: String): RelyingPartyRegistration {
if (registrationId != null) {
return this.registrations.findByRegistrationId(registrationId)
}
String entityId = resolveEntityIdFromSamlResponse(request)
for (val registration : this.registrations) {
if (registration.getAssertingPartyDetails().getEntityId().equals(entityId)) {
return registration
}
}
return null
}
private resolveEntityIdFromSamlResponse(val request: HttpServletRequest): String {
// ...
}
}
----
======
[TIP]
You might next take a look at how to use this resolver to customize xref:servlet/saml2/login/authentication.adoc#relyingpartyregistrationresolver-apply[`<saml2:Response>` authentication].
[[federating-saml2-login]]
=== Federating Login
@@ -926,7 +1036,6 @@ var registrations: Collection<RelyingPartyRegistration> = RelyingPartyRegistrati
.stream().map { builder : RelyingPartyRegistration.Builder -> builder
.registrationId(UUID.randomUUID().toString())
.entityId("https://example.org/saml2/sp")
.assertionConsumerServiceLocation("{baseUrl}/login/saml2/sso")
.build()
}
.collect(Collectors.toList()));
@@ -937,7 +1046,15 @@ Note that because the registration id is set to a random value, this will change
There are several ways to address this; let's focus on a way that suits the specific use case of federation.
In many federation cases, all the asserting parties share service provider configuration.
Given that Spring Security will by default include the `registrationId` in the service provider metadata, another step is to change corresponding URIs to exclude the `registrationId`, which you can see has already been done in the above sample where the `entityId` and `assertionConsumerServiceLocation` are configured with a static endpoint.
Given that Spring Security will by default include the `registrationId` in all many of its SAML 2.0 URIs, the next step is often to change these URIs to exclude the `registrationId`.
There are two main URIs you will want to change along those lines:
* <<relyingpartyregistrationresolver-entityid,Resolve by `<saml2:Response#Issuer>`>>
* <<relyingpartyregistrationresolver-single,Resolve with a default `RelyingPartyRegistration`>>
[NOTE]
Optionally, you may also want to change the Authentication Request location, but since this is a URI internal to the app and not published to asserting parties, the benefit is often minimal.
You can see a completed example of this in {gh-samples-url}/servlet/spring-boot/java/saml2/saml-extension-federation[our `saml-extension-federation` sample].

View File

@@ -132,9 +132,9 @@ To add other values, you can use delegation, like so:
[source,java]
----
@Bean
Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) {
OpenSaml4LogoutRequestResolver logoutRequestResolver =
new OpenSaml4LogoutRequestResolver(registrations);
Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationResolver registrationResolver) {
OpenSaml4LogoutRequestResolver logoutRequestResolver
new OpenSaml4LogoutRequestResolver(registrationResolver);
logoutRequestResolver.setParametersConsumer((parameters) -> {
String name = ((Saml2AuthenticatedPrincipal) parameters.getAuthentication().getPrincipal()).getFirstAttribute("CustomAttribute");
String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
@@ -175,9 +175,9 @@ To add other values, you can use delegation, like so:
[source,java]
----
@Bean
public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationRepository registrations) {
public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationResolver registrationResolver) {
OpenSaml4LogoutResponseResolver logoutRequestResolver =
new OpenSaml4LogoutResponseResolver(registrations);
new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
logoutRequestResolver.setParametersConsumer((parameters) -> {
if (checkOtherPrevailingConditions(parameters.getRequest())) {
parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT);

View File

@@ -35,7 +35,7 @@ val openSamlEntityDescriptor: EntityDescriptor = details.getEntityDescriptor();
[[publishing-relying-party-metadata]]
== Producing `<saml2:SPSSODescriptor>` Metadata
You can publish a metadata endpoint using the `saml2Metadata` DSL method, as you'll see below:
You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:
[tabs]
======
@@ -43,20 +43,33 @@ Java::
+
[source,java,role="primary"]
----
DefaultRelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository);
Saml2MetadataFilter filter = new Saml2MetadataFilter(
relyingPartyRegistrationResolver,
new OpenSamlMetadataResolver());
http
// ...
.saml2Login(withDefaults())
.saml2Metadata(withDefaults());
.addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
val relyingPartyRegistrationResolver: Converter<HttpServletRequest, RelyingPartyRegistration> =
DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository)
val filter = Saml2MetadataFilter(
relyingPartyRegistrationResolver,
OpenSamlMetadataResolver()
)
http {
//...
saml2Login { }
saml2Metadata { }
addFilterBefore<Saml2WebSsoAuthenticationFilter>(filter)
}
----
======
@@ -64,9 +77,8 @@ http {
You can use this metadata endpoint to register your relying party with your asserting party.
This is often as simple as finding the correct form field to supply the metadata endpoint.
By default, the metadata endpoint is `+/saml2/metadata+`, though it also responds to `+/saml2/metadata/{registrationId}+` and `+/saml2/service-provider-metadata/{registrationId}+`.
You can change this by calling the `metadataUrl` method in the DSL:
By default, the metadata endpoint is `+/saml2/service-provider-metadata/{registrationId}+`.
You can change this by calling the `setRequestMatcher` method on the filter:
[tabs]
======
@@ -74,22 +86,39 @@ Java::
+
[source,java,role="primary"]
----
.saml2Metadata((saml2) -> saml2.metadataUrl("/saml/metadata"))
filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata/{registrationId}", "GET"));
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
saml2Metadata {
metadataUrl = "/saml/metadata"
}
filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata/{registrationId}", "GET"))
----
======
Or, if you have registered a custom relying party registration resolver in the constructor, then you can specify a path without a `registrationId` hint, like so:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata", "GET"));
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata", "GET"))
----
======
== Changing the Way a `RelyingPartyRegistration` Is Looked Up
If you have a different strategy for identifying which `RelyingPartyRegistration` to use, you can configure your own `Saml2MetadataResponseResolver` like the one below:
To apply a custom `RelyingPartyRegistrationResolver` to the metadata endpoint, you can provide it directly in the filter constructor like so:
[tabs]
======
@@ -97,25 +126,38 @@ Java::
+
[source,java,role="primary"]
----
@Bean
Saml2MetadataResponseResolver metadataResponseResolver(RelyingPartyRegistrationRepository registrations) {
RequestMatcherMetadataResponseResolver metadata = new RequestMatcherMetadataResponseResolver(
(id) -> registrations.findByRegistrationId("relying-party"));
metadata.setMetadataFilename("metadata.xml");
return metadata;
}
----
RelyingPartyRegistrationResolver myRegistrationResolver = ...;
Saml2MetadataFilter metadata = new Saml2MetadataFilter(myRegistrationResolver, new OpenSamlMetadataResolver());
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun metadataResponseResolver(val registrations: RelyingPartyRegistrationRepository): Saml2MetadataResponseResolver {
val metadata = new RequestMatcherMetadataResponseResolver(
id: String -> registrations.findByRegistrationId("relying-party"))
metadata.setMetadataFilename("metadata.xml")
return metadata
}
// ...
http.addFilterBefore(metadata, BasicAuthenticationFilter.class);
----
======
.Kotlin
----
val myRegistrationResolver: RelyingPartyRegistrationResolver = ...;
val metadata = new Saml2MetadataFilter(myRegistrationResolver, OpenSamlMetadataResolver());
// ...
http.addFilterBefore(metadata, BasicAuthenticationFilter::class.java);
----
In the event that you are applying a `RelyingPartyRegistrationResolver` to remove the `registrationId` from the URI, you must also change the URI in the filter like so:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
metadata.setRequestMatcher("/saml2/metadata")
----
======
.Kotlin
----
metadata.setRequestMatcher("/saml2/metadata")
----

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,13 +1,65 @@
[[new]]
= What's New in Spring Security 6.2
= What's New in Spring Security 6.0
Spring Security 6.2 provides a number of new features.
Spring Security 6.0 provides a number of new features.
Below are the highlights of the release.
== Configuration
== Baseline Changes
* 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
* Spring Security 6 requires JDK 17
== Breaking Changes
* https://github.com/spring-projects/spring-security/issues/8980[gh-8980] - Remove unsafe/deprecated `Encryptors.querableText(CharSequence,CharSequence)`.
Instead use data storage to encrypt values.
* https://github.com/spring-projects/spring-security/issues/11520[gh-11520] - Remember Me uses SHA256 by default
* https://github.com/spring-projects/spring-security/issues/8819[gh-8819] - Move filters to web package
Reorganize imports
* https://github.com/spring-projects/spring-security/issues/7349[gh-7349] - Move filter and token to appropriate packages
Reorganize imports
* https://github.com/spring-projects/spring-security/issues/11026[gh-11026] - Use `RequestAttributeSecurityContextRepository` instead of `NullSecurityContextRepository`
* https://github.com/spring-projects/spring-security/pull/11887[gh-11827] - Change default authority for `oauth2Login()`
* https://github.com/spring-projects/spring-security/issues/10347[gh-10347] - Remove `UsernamePasswordAuthenticationToken` check in `BasicAuthenticationFilter`
* https://github.com/spring-projects/spring-security/pull/11923[gh-11923] - Remove `WebSecurityConfigurerAdapter`.
Instead, create a https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter[SecurityFilterChain bean].
* https://github.com/spring-projects/spring-security/issues/11899[gh-11899] - Use `MvcRequestMatcher` by default if Spring MVC is present.
You can configure a different `RequestMatcher` by using the https://docs.spring.io/spring-security/reference/servlet/appendix/namespace/http.html#nsa-http-attributes[request-matcher attribute from <http>].
* Change use-authorization-manager="true" to default
If the application uses `use-expressions="true"` or `access-decision-manager-ref` switch to `use-expressions="false"` or `authorization-manager-ref`, respectively.
If application relies on the implicit `<intercept-url pattern="/**" access="permitAll"/>`, this is no longer implicit and needs to be specified.
Or use `use-authorization-manager="false"`
* https://github.com/spring-projects/spring-security/issues/11939[gh-11939] - Remove deprecated `antMatchers`, `mvcMatchers`, `regexMatchers` helper methods from Java Configuration.
Instead, use `requestMatchers` or `HttpSecurity#securityMatchers`.
* https://github.com/spring-projects/spring-security/issues/11985[gh-11985] - Remove deprecated constructors in `Argon2PasswordEncoder`, `SCryptPasswordEncoder` and `Pbkdf2PasswordEncoder`.
* https://github.com/spring-projects/spring-security/issues/11960[gh-11960] - Default to Xor CSRF protection for xref:servlet/exploits/csrf.adoc#servlet-csrf-configure-request-handler[servlet] and xref:reactive/exploits/csrf.adoc#webflux-csrf-configure-request-handler[reactive]
* https://github.com/spring-projects/spring-security/issues/12019[gh-12019] - Remove deprecated method `setTokenFromMultipartDataEnabled` from `CsrfWebFilter`
* https://github.com/spring-projects/spring-security/issues/12020[gh-12020] - Remove deprecated method `tokenFromMultipartDataEnabled` from Java Configuration
* https://github.com/spring-projects/spring-security/issues/9429[gh-9429] - `Authentication(Web)Filter` rethrows `AuthenticationServiceException`s
* https://github.com/spring-projects/spring-security/issues/11027[gh-11027], https://github.com/spring-projects/spring-security/issues/11466[gh-11466] - Authorization on every dispatcher type
* https://github.com/spring-projects/spring-security/issues/11110[gh-11110] - Require explicit session saves by default
* https://github.com/spring-projects/spring-security/issues/11057[gh-11057] - Remove `MessageSourceAware` from `ExceptionTranslationWebFilter`
* https://github.com/spring-projects/spring-security/issues/12022[gh-12202] - Remove OAuth deprecations
* https://github.com/spring-projects/spring-security/issues/10556[gh-10556] - Remove EOL OpenSaml 3 Support.
Use the OpenSaml 4 Support instead.
* https://github.com/spring-projects/spring-security/issues/11077[gh-11077] - Remove SAML deprecations
** Remove `Converter` constructors from `Saml2MetadataFilter` and `Saml2AuthenticationTokenConverter`
** Remove `Saml2AuthenticationRequestContextResolver` and `Saml2AuthenticationRequestFactory` and implementations
** Remove `Saml2AuthenticationToken(String, String, String, String, List)`
** Remove `RelyingPartyRegistration.ProviderDetails` and related methods
** Remove `OpenSamlAuthenticationProvider`
* https://github.com/spring-projects/spring-security/issues/12180[gh-12180] - Register `FilterChainProxy` for all dispatcher types
== Core
* https://github.com/spring-projects/spring-security/issues/11446[gh-11446] - Add native image support for `@PreAuthorize`
* https://github.com/spring-projects/spring-security/issues/11737[gh-11737] - Add native image support for `@PostAuthorize`
* xref:servlet/integrations/observability.adoc[Instrumentation] of `AuthenticationManager`, `AuthorizationManager`, and `FilterChainProxy`
* xref:reactive/integrations/observability.adoc[Instrumentation] of `ReactiveAuthenticationManager`, `ReactiveAuthorizationManager`, and `WebFilterChainProxy`
== LDAP
* https://github.com/spring-projects/spring-security/pull/9276[gh-9276] - LdapAuthoritiesPopulator is post-processed
== Web
* https://github.com/spring-projects/spring-security/issues/11432[gh-11432] - `CookieServerCsrfTokenRepository` supports maxage