Browse Source

Document Multi-Factor Simple to Complex

This reworks the Multi-Factor documentation to start with the
simplest scenario and work to progressively more complex requirements.

Closes gh-18029
pull/18035/head
Rob Winch 4 months ago
parent
commit
e290c98e97
No known key found for this signature in database
  1. 2
      docs/modules/ROOT/nav.adoc
  2. 109
      docs/modules/ROOT/pages/servlet/authentication/adaptive.adoc
  3. 213
      docs/modules/ROOT/pages/servlet/authentication/mfa.adoc
  4. 2
      docs/modules/ROOT/pages/whats-new.adoc
  5. 6
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/authorizationmanagerfactory/UseAuthorizationManagerFactoryConfiguration.java
  6. 21
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/egmfa/EnableGlobalMultiFactorAuthenticationConfiguration.java
  7. 4
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/egmfa/EnableGlobalMultiFactorAuthenticationTests.java
  8. 9
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/hasallauthorities/ListAuthoritiesConfiguration.java
  9. 2
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/hasallauthorities/MultiFactorAuthenticationTests.java
  10. 79
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/hasallauthorities/MultipleAuthorizationRulesConfiguration.java
  11. 115
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/hasallauthorities/MultipleAuthorizationRulesConfigurationTests.java
  12. 25
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/programmaticmfa/AdminMfaAuthorizationManagerConfiguration.java
  13. 12
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/programmaticmfa/AdminMfaAuthorizationManagerConfigurationTests.java
  14. 76
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/raammfa/RequiredAuthoritiesAuthorizationManagerConfiguration.java
  15. 94
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/raammfa/RequiredAuthoritiesAuthorizationManagerConfigurationTests.java
  16. 25
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/selectivemfa/SelectiveMfaConfiguration.java
  17. 116
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/selectivemfa/SelectiveMfaConfigurationTests.java
  18. 6
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/authorizationmanagerfactory/UseAuthorizationManagerFactoryConfiguration.kt
  19. 20
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/egmfa/EnableGlobalMultiFactorAuthenticationConfiguration.kt
  20. 21
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/egmfa/EnableGlobalMultiFactorAuthenticationConfigurationTests.kt
  21. 9
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/hasallauthorities/ListAuthoritiesConfiguration.kt
  22. 2
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/hasallauthorities/MultiFactorAuthenticationTests.kt
  23. 19
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/hasallauthorities/MultipleAuthorizationRulesConfiguration.kt
  24. 115
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/hasallauthorities/MultipleAuthorizationRulesConfigurationTests.kt
  25. 22
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/programmaticmfa/AdminMfaAuthorizationManagerConfiguration.kt
  26. 12
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/programmaticmfa/AdminMfaAuthorizationManagerConfigurationTests.kt
  27. 76
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/raammfa/RequiredAuthoritiesAuthorizationManagerConfiguration.kt
  28. 100
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/raammfa/RequiredAuthoritiesAuthorizationManagerConfigurationTests.kt
  29. 32
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/selectivemfa/SelectiveMfaConfiguration.kt
  30. 127
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/selectivemfa/SelectiveMfaConfigurationTests.kt

2
docs/modules/ROOT/nav.adoc

@ -49,7 +49,7 @@ @@ -49,7 +49,7 @@
***** xref:servlet/authentication/passwords/password-encoder.adoc[PasswordEncoder]
***** xref:servlet/authentication/passwords/dao-authentication-provider.adoc[DaoAuthenticationProvider]
***** xref:servlet/authentication/passwords/ldap.adoc[LDAP]
*** xref:servlet/authentication/adaptive.adoc[Multifactor Authentication]
*** xref:servlet/authentication/mfa.adoc[Multi-Factor Authentication]
*** xref:servlet/authentication/persistence.adoc[Persistence]
*** xref:servlet/authentication/passkeys.adoc[Passkeys]
*** xref:servlet/authentication/onetimetoken.adoc[One-Time Token]

109
docs/modules/ROOT/pages/servlet/authentication/adaptive.adoc

@ -1,109 +0,0 @@ @@ -1,109 +0,0 @@
= Adaptive Authentication
Since authentication needs can vary from person-to-person and even from one login attempt to the next, Spring Security supports adapting authentication requirements to each situation.
Some of the most common applications of this principal are:
1. *Re-authentication* - Users need to provide authentication again in order to enter an area of elevated security
2. *Multi-factor Authentication* - Users need more than one authentication mechanism to pass in order to access secured resources
3. *Authorizing More Scopes* - Users are allowed to consent to a subset of scopes from an OAuth 2.0 Authorization Server.
Then, if later on a scope that they did not grant is needed, consent can be re-requested for just that scope.
4. *Opting-in to Stronger Authentication Mechanisms* - Users may not be ready yet to start using MFA, but the application wants to allow the subset of security-minded users to opt-in.
5. *Requiring Additional Steps for Suspicious Logins* - The application may notice that the user's IP address has changed, that they are behind a VPN, or some other consideration that requires additional verification
[[re-authentication]]
== Re-authentication
The most common of these is re-authentication.
Imagine an application configured in the following way:
include-code::./SimpleConfiguration[tag=httpSecurity,indent=0]
By default, this application has two authentication mechanisms that it allows, meaning that the user could use either one and be fully-authenticated.
If there is a set of endpoints that require a specific factor, we can specify that in `authorizeHttpRequests` as follows:
include-code::./RequireOttConfiguration[tag=httpSecurity,indent=0]
<1> - States that all `/profile/**` endpoints require one-time-token login to be authorized
Given the above configuration, users can log in with any mechanism that you support.
And, if they want to visit the profile page, then Spring Security will redirect them to the One-Time-Token Login page to obtain it.
In this way, the authority given to a user is directly proportional to the amount of proof given.
This adaptive approach allows users to give only the proof needed to perform their intended operations.
[[multi-factor-authentication]]
== Multi-Factor Authentication
You may require that all users require both One-Time-Token login and Username/Password login to access any part of your site.
To require both, you can state an authorization rule with `anyRequest` like so:
include-code::./ListAuthoritiesConfiguration[tag=httpSecurity,indent=0]
<1> - This states that both `FACTOR_PASSWORD` and `FACTOR_OTT` are needed to use any part of the application
Spring Security behind the scenes knows which endpoint to go to depending on which authority is missing.
If the user logged in initially with their username and password, then Spring Security redirects to the One-Time-Token Login page.
If the user logged in initially with a token, then Spring Security redirects to the Username/Password Login page.
[[authorization-manager-factory]]
=== Requiring MFA For All Endpoints
Specifying all authorities for each request pattern could be unwanted boilerplate:
include-code::./ListAuthoritiesEverywhereConfiguration[tag=httpSecurity,indent=0]
<1> - Since all authorities need to be specified for each endpoint, deploying MFA in this way can create unwanted boilerplate
This can be remedied by publishing an `AuthorizationManagerFactory` bean like so:
include-code::./UseAuthorizationManagerFactoryConfiguration[tag=authorizationManagerFactoryBean,indent=0]
This yields a more familiar configuration:
include-code::./UseAuthorizationManagerFactoryConfiguration[tag=httpSecurity,indent=0]
[[enable-global-mfa]]
=== @EnableGlobalMultiFactorAuthentication
You can simplify the configuration even further by using `@EnableGlobalMultiFactorAuthentication` to create the `AuthorizationManagerFactory` for you.
include-code::./EnableGlobalMultiFactorAuthenticationConfiguration[tag=enable-global-mfa,indent=0]
[[obtaining-more-authorization]]
== Authorizing More Scopes
You can also configure exception handling to direct Spring Security on how to obtain a missing scope.
Consider an application that requires a specific OAuth 2.0 scope for a given endpoint:
include-code::./ScopeConfiguration[tag=httpSecurity,indent=0]
If this is also configured with an `AuthorizationManagerFactory` bean like this one:
include-code::./MissingAuthorityConfiguration[tag=authorizationManagerFactoryBean,indent=0]
Then the application will require an X.509 certificate as well as authorization from an OAuth 2.0 authorization server.
In the event that the user does not consent to `profile:read`, this application as it stands will issue a 403.
However, if you have a way for the application to re-ask for consent, then you can implement this in an `AuthenticationEntryPoint` like the following:
include-code::./MissingAuthorityConfiguration[tag=authenticationEntryPoint,indent=0]
Then, your filter chain declaration can bind this entry point to the given authority like so:
include-code::./MissingAuthorityConfiguration[tag=httpSecurity,indent=0]
[[custom-authorization-manager-factory]]
== Programmatically Decide Which Authorities Are Required
`AuthorizationManager` is the core interface for making authorization decisions.
Consider an authorization manager that looks at the logged in user to decide which factors are necessary:
include-code::./CustomAuthorizationManagerFactory[tag=authorizationManager,indent=0]
In this case, using One-Time-Token is only required for those who have opted in.
This can then be enforced by a custom `AuthorizationManagerFactory` implementation:
include-code::./CustomAuthorizationManagerFactory[tag=authorizationManagerFactory,indent=0]

213
docs/modules/ROOT/pages/servlet/authentication/mfa.adoc

@ -0,0 +1,213 @@ @@ -0,0 +1,213 @@
= Multi-Factor Authentication
https://cheatsheetseries.owasp.org/cheatsheets/Multifactor_Authentication_Cheat_Sheet.html[Multi-Factor Authentication (MFA)] requires that a user provide factors in order to authenticate.
OWASP places factors into the following categories:
- Something the user knows (e.g. a password)
- Something that the user has (e.g. access to SMS or email)
- Something you are (e.g. biometrics)
- Somewhere you are (e.g. geolocation)
- Something you do (e.g. Behavior Profiling)
== `FactorGrantedAuthority`
At the time of authentication, Spring Security's authentication mechanisms add a javadoc:org.springframework.security.core.authority.FactorGrantedAuthority[] using the constants found in javadoc:org.springframework.security.core.GrantedAuthorities[].
For example, when a user authenticates using a password a `FactorGrantedAuthority` with the `authority` of `GrantedAuthorities.FACTOR_PASSWORD` is automatically added to the `Authentiation`.
In order to require MFA with Spring Security you must:
- Specify an authorization rule that requires multiple factors
- Setup authentication for each of those factors
[[egmfa]]
== @EnableGlobalMultiFactorAuthentication
javadoc:org.springframework.security.config.annotation.authorization.EnableGlobalMultiFactorAuthentication[format=annotation] simplifies Global MFA (the entire application requires MFA).
Below you can find a configuration that adds the requirement for both passwords and OTT to every authorization rule.
include-code::./EnableGlobalMultiFactorAuthenticationConfiguration[tag=enable-global-mfa,indent=0]
We are now able to concisely create a configuration that always requires multiple factors.
include-code::./EnableGlobalMultiFactorAuthenticationConfiguration[tag=httpSecurity,indent=0]
<1> URLs that begin with `/admin/**` require the authorities `FACTOR_OTT`, `FACTOR_PASSWORD`, `ROLE_ADMIN`.
<2> Every other URL requires the authorities `FACTOR_OTT`, `FACTOR_PASSWORD`
<3> Set up the authentication mechanisms that can provide the required factors.
Spring Security behind the scenes knows which endpoint to go to depending on which authority is missing.
If the user logged in initially with their username and password, then Spring Security redirects to the One-Time-Token Login page.
If the user logged in initially with a token, then Spring Security redirects to the Username/Password Login page.
[[authorization-manager-factory]]
== AuthorizationManagerFactory
The `@EnableGlobalMultiFactorAuthentication` annotation is just a shortcut for publishing an javadoc:org.springframework.security.authorization.AuthorizationManagerFactory[] Bean.
When an `AuthorizationManagerFactory` Bean is available, it is used by Spring Security to create authorization rules, like `hasAnyRole(String)`, that are defined on the `AuthorizationManagerFactory` Bean interface.
The implementation published by `@EnableGlobalMultiFactorAuthentication` will ensure that each authorization is combined with the requirement of having the specified factors.
The `AuthorizationManagerFactory` Bean below is what is published in the previously discussed xref:./mfa.adoc#using-egmfa[`@EnableGlobalMultiFactorAuthentication` example].
include-code::./UseAuthorizationManagerFactoryConfiguration[tag=authorizationManagerFactoryBean,indent=0]
[[selective-mfa]]
== Selectively Requiring MFA
We have demonstrated how to configure an entire application to require MFA (Global MFA) by using xref:./mfa.adoc#egmfa[`@EnableGlobalMultiFactorAuthentication`].
However, there are times that an application only wants parts of the application to require MFA.
Consider the following requirements:
- URLs that begin with `/admin/**` should require the authorities `FACTOR_OTT`, `FACTOR_PASSWORD`, `ROLE_ADMIN`.
- URLs that begin with `/user/settings` should require the authorities `FACTOR_OTT`, `FACTOR_PASSWORD`
- Every other URL requires an authenticated user
In this case, some URLs require MFA while others do not.
This means that the global approach that we saw before does not work.
Fortunately, we can use what we learned in xref:./mfa.adoc#authorization-manager-factory[] to solve this in a concise manner.
include-code::./SelectiveMfaConfiguration[tag=httpSecurity,indent=0]
<1> Create a `DefaultAuthorizationManagerFactory` as we did previously, but do not publish it as a Bean.
By not publishing it as a Bean, we are able to selectively use the `AuthorizationManagerFactory` instead of using it for every authorization rule.
<2> Explicitly use `AuthorizationManagerFactory` so that URLs that begin with `/admin/**` require `FACTOR_OTT`, `FACTOR_PASSWORD`, and `ROLE_ADMIN`.
<3> Explicitly use `AuthorizationManagerFactory` so that URLs that begin with `/user/settings` require `FACTOR_OTT` and `FACTOR_PASSWORD`
<4> Otherwise, the request must be authenticated.
There is no MFA requirement, because the `AuthorizationManagerFactory` is not used.
<5> Set up the authentication mechanisms that can provide the required factors.
[[programmatic-mfa]]
== Programmatic MFA
In our previous examples, MFA is a static decision per request.
There are times when we might want to require MFA for some users, but not others.
Determining if MFA is enabled per user can be achieved by creating a custom `AuthorizationManager` that conditionally requires factors based upon the `Authentication`.
include-code::./AdminMfaAuthorizationManagerConfiguration[tag=authorizationManager,indent=0]
<1> MFA is required for the user with the username `admin`
<2> Otherwise, MFA is not required
To enable the MFA rules globally, we can publish an `AuthorizationManagerFactory` Bean.
include-code::./AdminMfaAuthorizationManagerConfiguration[tag=authorizationManagerFactory,indent=0]
<1> Inject the custom `AuthorizationManager` as the javadoc:org.springframework.security.authorization.DefaultAuthorizationManagerFactory#setAdditionalAuthorization(org.springframework.security.authorization.AuthorizationManager)[DefaultAuthorization.additionalAuthorization].
This instructs `DefaultAuthorizationManagerFactory` that any authorization rule should apply our custom `AuthorizationManager` along with any authorization requirements defined by the application (e.g. `hasRole("ADMIN")).
<2> Publish `DefaultAuthorizationManagerFactory` as a Bean, so it is used globally
This should feel very similar to our previous example in xref:./mfa.adoc#authorization-manager-factory[].
The difference is that in the previous example, the `Builder` is setting `DefaultAuthorization.additionalAuthorization` with a built in `AuthorizationManager` that always requires the same authorities.
We can now define our authorization rules which are combined with `AdminMfaAuthorizationManager`.
include-code::./AdminMfaAuthorizationManagerConfiguration[tag=httpSecurity,indent=0]
<1> URLs that begin with `/admin/**` require `ROLE_ADMIN`.
If the username is `admin`, then `FACTOR_OTT` and `FACTOR_PASSWORD` are also required.
<2> Otherwise, the request must be authenticated.
If the username is `admin`, then `FACTOR_OTT` and `FACTOR_PASSWORD` are also required.
NOTE: MFA is enabled by username and not role because that is how we implemented `RequiredAuthoritiesAuthorizationManagerConfiguration`.
If we preferred, we could change our logic to enable MFA based upon the roles rather than the username.
[[raam-mfa]]
== RequiredAuthoritiesAuthorizationManager
We've demonstrated how we can dynamically determine the authorities for a particular user in xref:./mfa.adoc#programmatic-mfa[] using a custom `AuthorizationManager`.
However, this is such a common scenario that Spring Security provides built in support using javadoc:org.springframework.security.authorization.RequiredAuthoritiesAuthorizationManager[] and javadoc:org.springframework.security.authorization.RequiredAuthoritiesRepository[].
Let's implement the same requirement that we did in xref:./mfa.adoc#programmatic-mfa[] using the built-in support.
We start by creating the `RequiredAuthoritiesAuthorizationManager` Bean to use.
include-code::./RequiredAuthoritiesAuthorizationManagerConfiguration[tag=authorizationManager,indent=0]
<1> Create a javadoc:org.springframework.security.authorization.MapRequiredAuthoritiesRepository[] that maps users with the username `admin` to require MFA.
<2> Return a `RequiredAuthoritiesAuthorizationManager` that is injected with the `MapRequiredAuthoritiesRepository`.
Next we can define an `AuthorizationManagerFactory` that uses the `RequiredAuthoritiesAuthorizationManager`.
include-code::./RequiredAuthoritiesAuthorizationManagerConfiguration[tag=authorizationManagerFactory,indent=0]
<1> Inject the `RequiredAuthoritiesAuthorizationManager` as the javadoc:org.springframework.security.authorization.DefaultAuthorizationManagerFactory#setAdditionalAuthorization(org.springframework.security.authorization.AuthorizationManager)[DefaultAuthorization.additionalAuthorization].
This instructs `DefaultAuthorizationManagerFactory` that any authorization rule should apply `RequiredAuthoritiesAuthorizationManager` along with any authorization requirements defined by the application (e.g. `hasRole("ADMIN")).
<2> Publish `DefaultAuthorizationManagerFactory` as a Bean, so it is used globally
We can now define our authorization rules which are combined with `RequiredAuthoritiesAuthorizationManager`.
include-code::./RequiredAuthoritiesAuthorizationManagerConfiguration[tag=httpSecurity,indent=0]
<1> URLs that begin with `/admin/**` require `ROLE_ADMIN`.
If the username is `admin`, then `FACTOR_OTT` and `FACTOR_PASSWORD` are also required.
<2> Otherwise, the request must be authenticated.
If the username is `admin`, then `FACTOR_OTT` and `FACTOR_PASSWORD` are also required.
Our example uses an in memory mapping of usernames to the additional required authorities.
For more dynamic use cases that can be determined by the username, a custom implementation of javadoc:org.springframework.security.authorization.RequiredAuthoritiesRepository[] can be created.
Possible examples would be looking up if a user has enabled MFA in an explicit setting, determining if a user has registered a passkey, etc.
For cases that need to determine MFA based upon the `Authentication`, a custom `AuthorizationManger` can be used as demonstrated in xref:./mfa.adoc#programmatic-mfa[]
[[hasallauthorities]]
== Using hasAllAuthorities
We've shown a lot of additional infrastructure for supporting MFA.
However, for simple MFA use-cases, using `hasAllAuthorities` to require multiple factors is effective.
include-code::./ListAuthoritiesConfiguration[tag=httpSecurity,indent=0]
<1> Require `FACTOR_PASSWORD` and `FACTOR_OTT` for every request
<2> Set up the authentication mechanisms that can provide the required factors.
The configuration above works well only for the most simple use-cases.
If you have lots of endpoints, you probably do not want to repeat the requirements for MFA in every authorization rule.
For example, consider the following configuration:
include-code::./MultipleAuthorizationRulesConfiguration[tag=httpSecurity,indent=0]
<1> For URLs that begin with `/admin/**`, the following authorities are required `FACTOR_OTT`, `FACTOR_PASSWORD`, `ROLE_ADMIN`.
<2> For every other URL, the following authorities are required `FACTOR_OTT`, `FACTOR_PASSWORD`, `ROLE_USER`.
<3> Set up the authentication mechanisms that can provide the required factors.
The configuration only specifies two authorization rules, but it is enough to see that the duplication is not desirable.
Can you imagine what it would be like to declare hundreds of rules like this?
What's more that it becomes difficult to express more complicated authorization rules.
For example, how would you require two factors and either `ROLE_ADMIN` or `ROLE_USER`?
The answer to these questions, as we have already seen, is to use xref:./mfa.adoc#egmfa[]
[[re-authentication]]
== Re-authentication
The most common of these is re-authentication.
Imagine an application configured in the following way:
include-code::./SimpleConfiguration[tag=httpSecurity,indent=0]
By default, this application has two authentication mechanisms that it allows, meaning that the user could use either one and be fully-authenticated.
If there is a set of endpoints that require a specific factor, we can specify that in `authorizeHttpRequests` as follows:
include-code::./RequireOttConfiguration[tag=httpSecurity,indent=0]
<1> - States that all `/profile/**` endpoints require one-time-token login to be authorized
Given the above configuration, users can log in with any mechanism that you support.
And, if they want to visit the profile page, then Spring Security will redirect them to the One-Time-Token Login page to obtain it.
In this way, the authority given to a user is directly proportional to the amount of proof given.
This adaptive approach allows users to give only the proof needed to perform their intended operations.
[[obtaining-more-authorization]]
== Authorizing More Scopes
You can also configure exception handling to direct Spring Security on how to obtain a missing scope.
Consider an application that requires a specific OAuth 2.0 scope for a given endpoint:
include-code::./ScopeConfiguration[tag=httpSecurity,indent=0]
If this is also configured with an `AuthorizationManagerFactory` bean like this one:
include-code::./MissingAuthorityConfiguration[tag=authorizationManagerFactoryBean,indent=0]
Then the application will require an X.509 certificate as well as authorization from an OAuth 2.0 authorization server.
In the event that the user does not consent to `profile:read`, this application as it stands will issue a 403.
However, if you have a way for the application to re-ask for consent, then you can implement this in an `AuthenticationEntryPoint` like the following:
include-code::./MissingAuthorityConfiguration[tag=authenticationEntryPoint,indent=0]
Then, your filter chain declaration can bind this entry point to the given authority like so:
include-code::./MissingAuthorityConfiguration[tag=httpSecurity,indent=0]

2
docs/modules/ROOT/pages/whats-new.adoc

@ -15,7 +15,7 @@ Each section that follows will indicate the more notable removals as well as the @@ -15,7 +15,7 @@ Each section that follows will indicate the more notable removals as well as the
== Core
* Added Support for xref:servlet/authentication/adaptive.adoc[Multi-factor Authentication]
* Added Support for xref:servlet/authentication/mfa.adoc[Multi-Factor Authentication]
* Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize`
* Added javadoc:org.springframework.security.authorization.AllAuthoritiesAuthorizationManager[] and javadoc:org.springframework.security.authorization.AllAuthoritiesReactiveAuthorizationManager[] along with corresponding methods for xref:servlet/authorization/authorize-http-requests.adoc#authorize-requests[Authorizing `HttpServletRequests`] and xref:servlet/authorization/method-security.adoc#using-authorization-expression-fields-and-methods[method security expressions].
* Added xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`] for creating `AuthorizationManager` instances in xref:servlet/authorization/authorize-http-requests.adoc#customizing-authorization-managers[request-based] and xref:servlet/authorization/method-security.adoc#customizing-authorization-managers[method-based] authorization components

6
docs/src/test/java/org/springframework/security/docs/servlet/authentication/authorizationmanagerfactory/UseAuthorizationManagerFactoryConfiguration.java

@ -39,7 +39,11 @@ class UseAuthorizationManagerFactoryConfiguration { @@ -39,7 +39,11 @@ class UseAuthorizationManagerFactoryConfiguration {
@Bean
AuthorizationManagerFactory<Object> authz() {
return DefaultAuthorizationManagerFactory.builder()
.requireAdditionalAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY).build();
.requireAdditionalAuthorities(
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY
)
.build();
}
// end::authorizationManagerFactoryBean[]

21
docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationConfiguration.java → docs/src/test/java/org/springframework/security/docs/servlet/authentication/egmfa/EnableGlobalMultiFactorAuthenticationConfiguration.java

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package org.springframework.security.docs.servlet.authentication.enableglobalmfa;
package org.springframework.security.docs.servlet.authentication.egmfa;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -18,8 +18,8 @@ import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenG @@ -18,8 +18,8 @@ import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenG
@Configuration(proxyBeanMethods = false)
// tag::enable-global-mfa[]
@EnableGlobalMultiFactorAuthentication(authorities = {
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY })
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY })
// end::enable-global-mfa[]
public class EnableGlobalMultiFactorAuthenticationConfiguration {
@ -28,12 +28,15 @@ public class EnableGlobalMultiFactorAuthenticationConfiguration { @@ -28,12 +28,15 @@ public class EnableGlobalMultiFactorAuthenticationConfiguration {
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
.authorizeHttpRequests((authorize) -> authorize
// <1>
.requestMatchers("/admin/**").hasRole("ADMIN")
// <2>
.anyRequest().authenticated()
)
// <3>
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}

4
docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationTests.java → docs/src/test/java/org/springframework/security/docs/servlet/authentication/egmfa/EnableGlobalMultiFactorAuthenticationTests.java

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authentication.enableglobalmfa;
package org.springframework.security.docs.servlet.authentication.egmfa;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -52,7 +52,7 @@ public class EnableGlobalMultiFactorAuthenticationTests { @@ -52,7 +52,7 @@ public class EnableGlobalMultiFactorAuthenticationTests {
MockMvc mockMvc;
@Test
@WithMockUser(authorities = { GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY })
@WithMockUser(authorities = { GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_USER" })
void getWhenAuthenticatedWithPasswordAndOttThenPermits() throws Exception {
this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration.class, Http200Controller.class).autowire();
// @formatter:off

9
docs/src/test/java/org/springframework/security/docs/servlet/authentication/multifactorauthentication/ListAuthoritiesConfiguration.java → docs/src/test/java/org/springframework/security/docs/servlet/authentication/hasallauthorities/ListAuthoritiesConfiguration.java

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package org.springframework.security.docs.servlet.authentication.multifactorauthentication;
package org.springframework.security.docs.servlet.authentication.hasallauthorities;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -23,8 +23,13 @@ class ListAuthoritiesConfiguration { @@ -23,8 +23,13 @@ class ListAuthoritiesConfiguration {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY) // <1>
// <1>
.anyRequest().hasAllAuthorities(
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY
)
)
// <2>
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
// @formatter:on

2
docs/src/test/java/org/springframework/security/docs/servlet/authentication/multifactorauthentication/MultiFactorAuthenticationTests.java → docs/src/test/java/org/springframework/security/docs/servlet/authentication/hasallauthorities/MultiFactorAuthenticationTests.java

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authentication.multifactorauthentication;
package org.springframework.security.docs.servlet.authentication.hasallauthorities;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

79
docs/src/test/java/org/springframework/security/docs/servlet/authentication/hasallauthorities/MultipleAuthorizationRulesConfiguration.java

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authentication.hasallauthorities;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthorities;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class MultipleAuthorizationRulesConfiguration {
// tag::httpSecurity[]
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
// <1>
.requestMatchers("/admin/**").hasAllAuthorities(
"ROLE_ADMIN",
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY
)
// <2>
.anyRequest().hasAllAuthorities(
"ROLE_USER",
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY
)
)
// <3>
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}
// end::httpSecurity[]
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.authorities("app")
.build()
);
}
@Bean
OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler() {
return new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
}
}

115
docs/src/test/java/org/springframework/security/docs/servlet/authentication/hasallauthorities/MultipleAuthorizationRulesConfigurationTests.java

@ -0,0 +1,115 @@ @@ -0,0 +1,115 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authentication.hasallauthorities;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.GrantedAuthorities;
import org.springframework.security.docs.servlet.authentication.servletx509config.CustomX509Configuration;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests {@link CustomX509Configuration}.
*
* @author Rob Winch
*/
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
@TestExecutionListeners(WithSecurityContextTestExecutionListener.class)
public class MultipleAuthorizationRulesConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
MockMvc mockMvc;
@Test
@WithMockUser(authorities = { GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_USER" })
void getWhenAuthenticatedWithPasswordAndOttThenPermits() throws Exception {
this.spring.register(MultipleAuthorizationRulesConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(authenticated().withUsername("user"));
// @formatter:on
}
@Test
@WithMockUser(authorities = GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY)
void getWhenAuthenticatedWithPasswordThenRedirectsToOtt() throws Exception {
this.spring.register(MultipleAuthorizationRulesConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing"));
// @formatter:on
}
@Test
@WithMockUser(authorities = GrantedAuthorities.FACTOR_OTT_AUTHORITY)
void getWhenAuthenticatedWithOttThenRedirectsToPassword() throws Exception {
this.spring.register(MultipleAuthorizationRulesConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing"));
// @formatter:on
}
@Test
@WithMockUser
void getWhenAuthenticatedThenRedirectsToPassword() throws Exception {
this.spring.register(MultipleAuthorizationRulesConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing"));
// @formatter:on
}
@Test
void getWhenUnauthenticatedThenRedirectsToBoth() throws Exception {
this.spring.register(MultipleAuthorizationRulesConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login"));
// @formatter:on
}
@RestController
static class Http200Controller {
@GetMapping("/**")
String ok() {
return "ok";
}
}
}

25
docs/src/test/java/org/springframework/security/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactory.java → docs/src/test/java/org/springframework/security/docs/servlet/authentication/programmaticmfa/AdminMfaAuthorizationManagerConfiguration.java

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package org.springframework.security.docs.servlet.authentication.customauthorizationmanagerfactory;
package org.springframework.security.docs.servlet.authentication.programmaticmfa;
import java.util.function.Supplier;
@ -6,7 +6,7 @@ import org.jspecify.annotations.Nullable; @@ -6,7 +6,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AllAuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component;
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class CustomAuthorizationManagerFactory {
class AdminMfaAuthorizationManagerConfiguration {
// tag::httpSecurity[]
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
@ -46,13 +46,19 @@ class CustomAuthorizationManagerFactory { @@ -46,13 +46,19 @@ class CustomAuthorizationManagerFactory {
// tag::authorizationManager[]
@Component
class UserBasedOttAuthorizationManager implements AuthorizationManager<Object> {
class AdminMfaAuthorizationManager implements AuthorizationManager<Object> {
@Override
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, Object context) {
if ("admin".equals(authentication.get().getName())) {
return AuthorityAuthorizationManager.hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY)
.authorize(authentication, context);
AuthorizationManager<Object> admins =
AllAuthoritiesAuthorizationManager.hasAllAuthorities(
GrantedAuthorities.FACTOR_OTT_AUTHORITY,
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY
);
// <1>
return admins.authorize(authentication, context);
} else {
// <2>
return new AuthorizationDecision(true);
}
}
@ -61,9 +67,12 @@ class CustomAuthorizationManagerFactory { @@ -61,9 +67,12 @@ class CustomAuthorizationManagerFactory {
// tag::authorizationManagerFactory[]
@Bean
AuthorizationManagerFactory<Object> authorizationManagerFactory(UserBasedOttAuthorizationManager optIn) {
AuthorizationManagerFactory<Object> authorizationManagerFactory(
AdminMfaAuthorizationManager admins) {
DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
defaults.setAdditionalAuthorization(optIn);
// <1>
defaults.setAdditionalAuthorization(admins);
// <2>
return defaults;
}
// end::authorizationManagerFactory[]

12
docs/src/test/java/org/springframework/security/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.java → docs/src/test/java/org/springframework/security/docs/servlet/authentication/programmaticmfa/AdminMfaAuthorizationManagerConfigurationTests.java

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authentication.customauthorizationmanagerfactory;
package org.springframework.security.docs.servlet.authentication.programmaticmfa;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -44,7 +44,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @@ -44,7 +44,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@ExtendWith({SpringExtension.class, SpringTestContextExtension.class})
@TestExecutionListeners(WithSecurityContextTestExecutionListener.class)
public class CustomAuthorizationManagerFactoryTests {
public class AdminMfaAuthorizationManagerConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@ -54,7 +54,7 @@ public class CustomAuthorizationManagerFactoryTests { @@ -54,7 +54,7 @@ public class CustomAuthorizationManagerFactoryTests {
@Test
@WithMockUser(username = "admin")
void getWhenAdminThenRedirectsToOtt() throws Exception {
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
this.spring.register(AdminMfaAuthorizationManagerConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
@ -65,7 +65,7 @@ public class CustomAuthorizationManagerFactoryTests { @@ -65,7 +65,7 @@ public class CustomAuthorizationManagerFactoryTests {
@Test
@WithMockUser
void getWhenNotAdminThenAllows() throws Exception {
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
this.spring.register(AdminMfaAuthorizationManagerConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
@ -74,9 +74,9 @@ public class CustomAuthorizationManagerFactoryTests { @@ -74,9 +74,9 @@ public class CustomAuthorizationManagerFactoryTests {
}
@Test
@WithMockUser(username = "admin", authorities = GrantedAuthorities.FACTOR_OTT_AUTHORITY)
@WithMockUser(username = "admin", authorities = { GrantedAuthorities.FACTOR_OTT_AUTHORITY, GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY })
void getWhenAdminAndHasFactorThenAllows() throws Exception {
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
this.spring.register(AdminMfaAuthorizationManagerConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())

76
docs/src/test/java/org/springframework/security/docs/servlet/authentication/raammfa/RequiredAuthoritiesAuthorizationManagerConfiguration.java

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
package org.springframework.security.docs.servlet.authentication.raammfa;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.authorization.MapRequiredAuthoritiesRepository;
import org.springframework.security.authorization.RequiredAuthoritiesAuthorizationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthorities;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class RequiredAuthoritiesAuthorizationManagerConfiguration {
// tag::httpSecurity[]
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}
// end::httpSecurity[]
// tag::authorizationManager[]
@Bean
RequiredAuthoritiesAuthorizationManager<Object> adminAuthorization() {
// <1>
MapRequiredAuthoritiesRepository authorities = new MapRequiredAuthoritiesRepository();
authorities.saveRequiredAuthorities("admin", List.of(
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY)
);
// <2>
return new RequiredAuthoritiesAuthorizationManager<>(authorities);
}
// end::authorizationManager[]
// tag::authorizationManagerFactory[]
@Bean
AuthorizationManagerFactory<Object> authorizationManagerFactory(
RequiredAuthoritiesAuthorizationManager admins) {
DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
// <1>
defaults.setAdditionalAuthorization(admins);
// <2>
return defaults;
}
// end::authorizationManagerFactory[]
@Bean
public UserDetailsService users() {
return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin());
}
@Bean
OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler() {
return new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
}
}

94
docs/src/test/java/org/springframework/security/docs/servlet/authentication/raammfa/RequiredAuthoritiesAuthorizationManagerConfigurationTests.java

@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authentication.programmaticmfa;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.GrantedAuthorities;
import org.springframework.security.docs.servlet.authentication.servletx509config.CustomX509Configuration;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests {@link CustomX509Configuration}.
*
* @author Rob Winch
*/
@ExtendWith({SpringExtension.class, SpringTestContextExtension.class})
@TestExecutionListeners(WithSecurityContextTestExecutionListener.class)
public class RequiredAuthoritiesAuthorizationManagerConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
MockMvc mockMvc;
@Test
@WithMockUser(username = "admin")
void getWhenAdminThenRedirectsToOtt() throws Exception {
this.spring.register(AdminMfaAuthorizationManagerConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing"));
// @formatter:on
}
@Test
@WithMockUser
void getWhenNotAdminThenAllows() throws Exception {
this.spring.register(AdminMfaAuthorizationManagerConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(authenticated().withUsername("user"));
// @formatter:on
}
@Test
@WithMockUser(username = "admin", authorities = { GrantedAuthorities.FACTOR_OTT_AUTHORITY, GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY })
void getWhenAdminAndHasFactorThenAllows() throws Exception {
this.spring.register(AdminMfaAuthorizationManagerConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(authenticated().withUsername("admin"));
// @formatter:on
}
@RestController
static class Http200Controller {
@GetMapping("/**")
String ok() {
return "ok";
}
}
}

25
docs/src/test/java/org/springframework/security/docs/servlet/authentication/authorizationmanagerfactory/ListAuthoritiesEverywhereConfiguration.java → docs/src/test/java/org/springframework/security/docs/servlet/authentication/selectivemfa/SelectiveMfaConfiguration.java

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
package org.springframework.security.docs.servlet.authentication.authorizationmanagerfactory;
package org.springframework.security.docs.servlet.authentication.selectivemfa;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@ -15,17 +17,30 @@ import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenG @@ -15,17 +17,30 @@ import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenG
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class ListAuthoritiesEverywhereConfiguration {
class SelectiveMfaConfiguration {
// tag::httpSecurity[]
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
// <1>
AuthorizationManagerFactory<Object> mfa =
DefaultAuthorizationManagerFactory.<Object>builder()
.requireAdditionalAuthorities(
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY
)
.build();
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/admin/**").hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_ADMIN") // <1>
.anyRequest().hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY)
// <2>
.requestMatchers("/admin/**").access(mfa.hasRole("ADMIN"))
// <3>
.requestMatchers("/user/settings/**").access(mfa.authenticated())
// <4>
.anyRequest().authenticated()
)
// <5>
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
// @formatter:on

116
docs/src/test/java/org/springframework/security/docs/servlet/authentication/selectivemfa/SelectiveMfaConfigurationTests.java

@ -0,0 +1,116 @@ @@ -0,0 +1,116 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authentication.selectivemfa;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.GrantedAuthorities;
import org.springframework.security.docs.servlet.authentication.servletx509config.CustomX509Configuration;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests {@link CustomX509Configuration}.
*
* @author Rob Winch
*/
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
@TestExecutionListeners(WithSecurityContextTestExecutionListener.class)
public class SelectiveMfaConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
MockMvc mockMvc;
@Test
@WithMockUser(authorities = { GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, "ROLE_ADMIN" })
void adminWhenMissingOttThenRequired() throws Exception {
this.spring.register(SelectiveMfaConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/admin/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("http://localhost/login?*"));
// @formatter:on
}
@Test
@WithMockUser(authorities = { GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_ADMIN" })
void adminWhenMfaThenAllowed() throws Exception {
this.spring.register(SelectiveMfaConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/admin/"))
.andExpect(status().isOk())
.andExpect(authenticated().withUsername("user"));
// @formatter:on
}
@Test
@WithMockUser(authorities = { GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, "ROLE_ADMIN" })
void userSettingsRequiresMfa() throws Exception {
this.spring.register(SelectiveMfaConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/admin/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing"));
// @formatter:on
}
@Test
@WithMockUser(authorities = { GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, "ROLE_USER" })
void userSettingsWhenMissingOttThenRequired() throws Exception {
this.spring.register(SelectiveMfaConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/user/settings/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("http://localhost/login?*"));
// @formatter:on
}
@Test
@WithMockUser(roles = "USER")
void rootDoesNotRequireMfa() throws Exception {
this.spring.register(SelectiveMfaConfiguration.class, Http200Controller.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/"))
.andExpect(status().isOk());
// @formatter:on
}
@RestController
static class Http200Controller {
@GetMapping("/**")
String ok() {
return "ok";
}
}
}

6
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/authorizationmanagerfactory/UseAuthorizationManagerFactoryConfiguration.kt

@ -39,7 +39,11 @@ internal class UseAuthorizationManagerFactoryConfiguration { @@ -39,7 +39,11 @@ internal class UseAuthorizationManagerFactoryConfiguration {
@Bean
fun authz(): AuthorizationManagerFactory<Object> {
return DefaultAuthorizationManagerFactory.builder<Object>()
.requireAdditionalAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY).build()
.requireAdditionalAuthorities(
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY
)
.build()
}
// end::authorizationManagerFactoryBean[]

20
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/ListAuthoritiesEverywhereConfiguration.kt → docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/egmfa/EnableGlobalMultiFactorAuthenticationConfiguration.kt

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
package org.springframework.security.kt.docs.servlet.authentication.enableglobalmfa
package org.springframework.security.kt.docs.servlet.authentication.egmfa
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.authorization.EnableGlobalMultiFactorAuthentication
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
@ -15,7 +16,13 @@ import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenG @@ -15,7 +16,13 @@ import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenG
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class ListAuthoritiesEverywhereConfiguration {
// tag::enable-global-mfa[]
@EnableGlobalMultiFactorAuthentication( authorities = [
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY])
// end::enable-global-mfa[]
internal class EnableGlobalMultiFactorAuthenticationConfiguration {
// tag::httpSecurity[]
@Bean
@ -23,9 +30,12 @@ class ListAuthoritiesEverywhereConfiguration { @@ -23,9 +30,12 @@ class ListAuthoritiesEverywhereConfiguration {
// @formatter:off
http {
authorizeHttpRequests {
authorize("/admin/**", hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_ADMIN")) // <1>
authorize(anyRequest, hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY))
// <1>
authorize("/admin/**", hasRole("ADMIN"))
// <2>
authorize(anyRequest, authenticated)
}
// <3>
formLogin { }
oneTimeTokenLogin { }
}
@ -34,8 +44,6 @@ class ListAuthoritiesEverywhereConfiguration { @@ -34,8 +44,6 @@ class ListAuthoritiesEverywhereConfiguration {
}
// end::httpSecurity[]
// end::httpSecurity[]
@Bean
fun userDetailsService(): UserDetailsService {
return InMemoryUserDetailsManager(

21
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/AuthorizationManagerFactoryTests.kt → docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/egmfa/EnableGlobalMultiFactorAuthenticationConfigurationTests.kt

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authentication.enableglobalmfa
package org.springframework.security.kt.docs.servlet.authentication.egmfa
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ -39,7 +39,7 @@ import org.springframework.web.bind.annotation.RestController @@ -39,7 +39,7 @@ import org.springframework.web.bind.annotation.RestController
*/
@ExtendWith(SpringExtension::class, SpringTestContextExtension::class)
@TestExecutionListeners(WithSecurityContextTestExecutionListener::class)
class AuthorizationManagerFactoryTests {
class EnableGlobalMultiFactorAuthenticationConfigurationTests {
@JvmField
val spring: SpringTestContext = SpringTestContext(this)
@ -47,11 +47,10 @@ class AuthorizationManagerFactoryTests { @@ -47,11 +47,10 @@ class AuthorizationManagerFactoryTests {
var mockMvc: MockMvc? = null
@Test
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY])
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_ADMIN"])
@Throws(Exception::class)
fun getWhenAuthenticatedWithPasswordAndOttThenPermits() {
this.spring.register(UseAuthorizationManagerFactoryConfiguration::class.java, Http200Controller::class.java)
.autowire()
this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk())
@ -63,8 +62,7 @@ class AuthorizationManagerFactoryTests { @@ -63,8 +62,7 @@ class AuthorizationManagerFactoryTests {
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY])
@Throws(Exception::class)
fun getWhenAuthenticatedWithPasswordThenRedirectsToOtt() {
this.spring.register(UseAuthorizationManagerFactoryConfiguration::class.java, Http200Controller::class.java)
.autowire()
this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
@ -76,8 +74,7 @@ class AuthorizationManagerFactoryTests { @@ -76,8 +74,7 @@ class AuthorizationManagerFactoryTests {
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_OTT_AUTHORITY])
@Throws(Exception::class)
fun getWhenAuthenticatedWithOttThenRedirectsToPassword() {
this.spring.register(UseAuthorizationManagerFactoryConfiguration::class.java, Http200Controller::class.java)
.autowire()
this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
@ -89,8 +86,7 @@ class AuthorizationManagerFactoryTests { @@ -89,8 +86,7 @@ class AuthorizationManagerFactoryTests {
@WithMockUser
@Throws(Exception::class)
fun getWhenAuthenticatedThenRedirectsToPassword() {
this.spring.register(UseAuthorizationManagerFactoryConfiguration::class.java, Http200Controller::class.java)
.autowire()
this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
@ -101,8 +97,7 @@ class AuthorizationManagerFactoryTests { @@ -101,8 +97,7 @@ class AuthorizationManagerFactoryTests {
@Test
@Throws(Exception::class)
fun getWhenUnauthenticatedThenRedirectsToBoth() {
this.spring.register(UseAuthorizationManagerFactoryConfiguration::class.java, Http200Controller::class.java)
.autowire()
this.spring.register(EnableGlobalMultiFactorAuthenticationConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())

9
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/multifactorauthentication/ListAuthoritiesConfiguration.kt → docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/hasallauthorities/ListAuthoritiesConfiguration.kt

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package org.springframework.security.kt.docs.servlet.authentication.multifactorauthentication
package org.springframework.security.kt.docs.servlet.authentication.hasallauthorities
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@ -23,8 +23,13 @@ internal class ListAuthoritiesConfiguration { @@ -23,8 +23,13 @@ internal class ListAuthoritiesConfiguration {
// @formatter:off
http {
authorizeHttpRequests {
authorize(anyRequest, hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY))
// <1>
authorize(anyRequest, hasAllAuthorities(
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY
))
}
// <2>
formLogin { }
oneTimeTokenLogin { }
}

2
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/multifactorauthentication/MultiFactorAuthenticationTests.kt → docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/hasallauthorities/MultiFactorAuthenticationTests.kt

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authentication.multifactorauthentication
package org.springframework.security.kt.docs.servlet.authentication.hasallauthorities
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

19
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/authorizationmanagerfactory/ListAuthoritiesEverywhereConfiguration.kt → docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/hasallauthorities/MultipleAuthorizationRulesConfiguration.kt

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package org.springframework.security.kt.docs.servlet.authentication.authorizationmanagerfactory
package org.springframework.security.kt.docs.servlet.authentication.hasallauthorities
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@ -15,7 +15,7 @@ import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenG @@ -15,7 +15,7 @@ import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenG
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class ListAuthoritiesEverywhereConfiguration {
internal class MultipleAuthorizationRulesConfiguration {
// tag::httpSecurity[]
@Bean
@ -23,9 +23,20 @@ class ListAuthoritiesEverywhereConfiguration { @@ -23,9 +23,20 @@ class ListAuthoritiesEverywhereConfiguration {
// @formatter:off
http {
authorizeHttpRequests {
authorize("/admin/**", hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_ADMIN")) // <1>
authorize(anyRequest, hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY))
// <1>
authorize("/admin/**", hasAllAuthorities(
"ROLE_ADMIN",
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY
))
// <2>
authorize(anyRequest, hasAllAuthorities(
"ROLE_USER",
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY
))
}
// <3>
formLogin { }
oneTimeTokenLogin { }
}

115
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/hasallauthorities/MultipleAuthorizationRulesConfigurationTests.kt

@ -0,0 +1,115 @@ @@ -0,0 +1,115 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authentication.hasallauthorities
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.core.GrantedAuthorities
import org.springframework.security.test.context.support.WithMockUser
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
import org.springframework.test.context.TestExecutionListeners
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
/**
* Tests [CustomX509Configuration].
*
* @author Rob Winch
*/
@ExtendWith(SpringExtension::class, SpringTestContextExtension::class)
@TestExecutionListeners(WithSecurityContextTestExecutionListener::class)
class MultipleAuthorizationRulesConfigurationTests {
@JvmField
val spring: SpringTestContext = SpringTestContext(this)
@Autowired
var mockMvc: MockMvc? = null
@Test
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_USER"])
@Throws(Exception::class)
fun getWhenAuthenticatedWithPasswordAndOttThenPermits() {
this.spring.register(MultipleAuthorizationRulesConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(SecurityMockMvcResultMatchers.authenticated().withUsername("user"))
// @formatter:on
}
@Test
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY])
@Throws(Exception::class)
fun getWhenAuthenticatedWithPasswordThenRedirectsToOtt() {
this.spring.register(MultipleAuthorizationRulesConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
.andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing"))
// @formatter:on
}
@Test
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_OTT_AUTHORITY])
@Throws(Exception::class)
fun getWhenAuthenticatedWithOttThenRedirectsToPassword() {
this.spring.register(MultipleAuthorizationRulesConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
.andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing"))
// @formatter:on
}
@Test
@WithMockUser
@Throws(Exception::class)
fun getWhenAuthenticatedThenRedirectsToPassword() {
this.spring.register(MultipleAuthorizationRulesConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
.andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing"))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getWhenUnauthenticatedThenRedirectsToBoth() {
this.spring.register(MultipleAuthorizationRulesConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
.andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login"))
// @formatter:on
}
@RestController
internal class Http200Controller {
@GetMapping("/**")
fun ok(): String {
return "ok"
}
}
}

22
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactory.kt → docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/programmaticmfa/AdminMfaAuthorizationManagerConfiguration.kt

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package org.springframework.security.kt.docs.servlet.authentication.customauthorizationmanagerfactory
package org.springframework.security.kt.docs.servlet.authentication.programmaticmfa
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@ -19,7 +19,7 @@ import java.util.function.Supplier @@ -19,7 +19,7 @@ import java.util.function.Supplier
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
internal class CustomAuthorizationManagerFactory {
internal class AdminMfaAuthorizationManagerConfiguration {
// tag::httpSecurity[]
@Bean
@ -40,13 +40,19 @@ internal class CustomAuthorizationManagerFactory { @@ -40,13 +40,19 @@ internal class CustomAuthorizationManagerFactory {
// tag::authorizationManager[]
@Component
internal open class UserBasedOttAuthorizationManager : AuthorizationManager<Object> {
internal open class AdminMfaAuthorizationManager : AuthorizationManager<Object> {
override fun authorize(
authentication: Supplier<out Authentication?>, context: Object): AuthorizationResult {
return if ("admin" == authentication.get().name) {
AuthorityAuthorizationManager.hasAuthority<Object>(GrantedAuthorities.FACTOR_OTT_AUTHORITY)
.authorize(authentication, context)
var admins =
AllAuthoritiesAuthorizationManager.hasAllAuthorities<Any>(
GrantedAuthorities.FACTOR_OTT_AUTHORITY,
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY
)
// <1>
admins.authorize(authentication, context)
} else {
// <2>
AuthorizationDecision(true)
}
}
@ -55,9 +61,11 @@ internal class CustomAuthorizationManagerFactory { @@ -55,9 +61,11 @@ internal class CustomAuthorizationManagerFactory {
// tag::authorizationManagerFactory[]
@Bean
fun authorizationManagerFactory(optIn: UserBasedOttAuthorizationManager?): AuthorizationManagerFactory<Object> {
fun authorizationManagerFactory(admins: AdminMfaAuthorizationManager): AuthorizationManagerFactory<Object> {
val defaults = DefaultAuthorizationManagerFactory<Object>()
defaults.setAdditionalAuthorization(optIn)
// <1>
defaults.setAdditionalAuthorization(admins)
// <2>
return defaults
}
// end::authorizationManagerFactory[]

12
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.kt → docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/programmaticmfa/AdminMfaAuthorizationManagerConfigurationTests.kt

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authentication.customauthorizationmanagerfactory
package org.springframework.security.kt.docs.servlet.authentication.programmaticmfa
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ -40,7 +40,7 @@ import org.springframework.web.bind.annotation.RestController @@ -40,7 +40,7 @@ import org.springframework.web.bind.annotation.RestController
*/
@ExtendWith(SpringExtension::class, SpringTestContextExtension::class)
@TestExecutionListeners(WithSecurityContextTestExecutionListener::class)
class CustomAuthorizationManagerFactoryTests {
class AdminMfaAuthorizationManagerConfigurationTests {
@JvmField
val spring: SpringTestContext = SpringTestContext(this)
@ -51,7 +51,7 @@ class CustomAuthorizationManagerFactoryTests { @@ -51,7 +51,7 @@ class CustomAuthorizationManagerFactoryTests {
@Throws(Exception::class)
@WithMockUser(username = "admin")
fun getWhenAdminThenRedirectsToOtt() {
this.spring.register(CustomAuthorizationManagerFactory::class.java, Http200Controller::class.java).autowire()
this.spring.register(AdminMfaAuthorizationManagerConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(get("/"))
.andExpect(status().is3xxRedirection())
@ -63,7 +63,7 @@ class CustomAuthorizationManagerFactoryTests { @@ -63,7 +63,7 @@ class CustomAuthorizationManagerFactoryTests {
@Throws(Exception::class)
@WithMockUser
fun getWhenNotAdminThenAllows() {
this.spring.register(CustomAuthorizationManagerFactory::class.java, Http200Controller::class.java).autowire()
this.spring.register(AdminMfaAuthorizationManagerConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(get("/"))
.andExpect(status().isOk())
@ -73,9 +73,9 @@ class CustomAuthorizationManagerFactoryTests { @@ -73,9 +73,9 @@ class CustomAuthorizationManagerFactoryTests {
@Test
@Throws(Exception::class)
@WithMockUser(username = "admin", authorities = [GrantedAuthorities.FACTOR_OTT_AUTHORITY])
@WithMockUser(username = "admin", authorities = [GrantedAuthorities.FACTOR_OTT_AUTHORITY, GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY ])
fun getWhenAdminAndHasFactorThenAllows() {
this.spring.register(CustomAuthorizationManagerFactory::class.java, Http200Controller::class.java).autowire()
this.spring.register(AdminMfaAuthorizationManagerConfiguration::class.java, Http200Controller::class.java).autowire()
// @formatter:off
this.mockMvc!!.perform(get("/"))
.andExpect(status().isOk())

76
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/raammfa/RequiredAuthoritiesAuthorizationManagerConfiguration.kt

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
package org.springframework.security.kt.docs.servlet.authentication.raammfa
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authorization.AuthorizationManagerFactory
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
import org.springframework.security.authorization.MapRequiredAuthoritiesRepository
import org.springframework.security.authorization.RequiredAuthoritiesAuthorizationManager
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.core.GrantedAuthorities
import org.springframework.security.core.userdetails.PasswordEncodedUser
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
import java.util.List
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
internal class RequiredAuthoritiesAuthorizationManagerConfiguration {
// tag::httpSecurity[]
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
// @formatter:off
http {
authorizeHttpRequests {
authorize("/admin/**", hasRole("ADMIN"))
authorize(anyRequest, authenticated)
}
formLogin { }
oneTimeTokenLogin { }
}
// @formatter:on
return http.build()
}
// end::httpSecurity[]
// tag::authorizationManager[]
@Bean
fun adminAuthorization(): RequiredAuthoritiesAuthorizationManager<Object> {
// <1>
val authorities = MapRequiredAuthoritiesRepository()
authorities.saveRequiredAuthorities("admin", List.of(
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY)
)
// <2>
return RequiredAuthoritiesAuthorizationManager(authorities)
}
// end::authorizationManager[]
// tag::authorizationManagerFactory[]
@Bean
fun authorizationManagerFactory(admins: RequiredAuthoritiesAuthorizationManager<Object>): AuthorizationManagerFactory<Object> {
val defaults = DefaultAuthorizationManagerFactory<Object>()
// <1>
defaults.setAdditionalAuthorization(admins)
// <2>
return defaults
}
// end::authorizationManagerFactory[]
@Bean
fun users(): UserDetailsService {
return InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin())
}
@Bean
fun tokenGenerationSuccessHandler(): OneTimeTokenGenerationSuccessHandler {
return RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent")
}
}

100
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/raammfa/RequiredAuthoritiesAuthorizationManagerConfigurationTests.kt

@ -0,0 +1,100 @@ @@ -0,0 +1,100 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authentication.raammfa
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.core.GrantedAuthorities
import org.springframework.security.test.context.support.WithMockUser
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
import org.springframework.test.context.TestExecutionListeners
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
/**
* Tests [CustomX509Configuration].
*
* @author Rob Winch
*/
@ExtendWith(SpringExtension::class, SpringTestContextExtension::class)
@TestExecutionListeners(WithSecurityContextTestExecutionListener::class)
class RequiredAuthoritiesAuthorizationManagerConfigurationTests {
@JvmField
val spring: SpringTestContext = SpringTestContext(this)
@Autowired
var mockMvc: MockMvc? = null
@Test
@WithMockUser(username = "admin")
@Throws(Exception::class)
fun getWhenAdminThenRedirectsToOtt() {
this.spring.register(RequiredAuthoritiesAuthorizationManagerConfiguration::class.java, Http200Controller::class.java)
.autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
// @formatter:on
}
@Test
@WithMockUser
@Throws(Exception::class)
fun getWhenNotAdminThenAllows() {
this.spring.register(RequiredAuthoritiesAuthorizationManagerConfiguration::class.java, Http200Controller::class.java)
.autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(SecurityMockMvcResultMatchers.authenticated().withUsername("user"))
// @formatter:on
}
@Test
@WithMockUser(
username = "admin",
authorities = [GrantedAuthorities.FACTOR_OTT_AUTHORITY, GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY]
)
@Throws(
Exception::class
)
fun getWhenAdminAndHasFactorThenAllows() {
this.spring.register(RequiredAuthoritiesAuthorizationManagerConfiguration::class.java, Http200Controller::class.java)
.autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(SecurityMockMvcResultMatchers.authenticated().withUsername("admin"))
// @formatter:on
}
@RestController
internal class Http200Controller {
@GetMapping("/**")
fun ok(): String {
return "ok"
}
}
}

32
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/UseAuthorizationManagerFactoryConfiguration.kt → docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/selectivemfa/SelectiveMfaConfiguration.kt

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package org.springframework.security.kt.docs.servlet.authentication.enableglobalmfa
package org.springframework.security.kt.docs.servlet.authentication.selectivemfa
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@ -17,32 +17,38 @@ import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenG @@ -17,32 +17,38 @@ import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenG
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
internal class UseAuthorizationManagerFactoryConfiguration {
internal class SelectiveMfaConfiguration {
// tag::httpSecurity[]
@Bean
@Throws(Exception::class)
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
// @formatter:off
// <1>
val mfa: AuthorizationManagerFactory<Any> =
DefaultAuthorizationManagerFactory.builder<Any>()
.requireAdditionalAuthorities(
GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY,
GrantedAuthorities.FACTOR_OTT_AUTHORITY
)
.build()
http {
authorizeHttpRequests {
authorize("/admin/**", hasRole("ADMIN"))
// <2>
authorize("/admin/**", mfa.hasRole("ADMIN"))
// <3>
authorize("/user/settings/**", mfa.authenticated())
// <4>
authorize(anyRequest, authenticated)
}
// <5>
formLogin { }
oneTimeTokenLogin { }
oneTimeTokenLogin { }
}
// @formatter:on
return http.build()
}
// end::httpSecurity[]
// tag::authorizationManagerFactoryBean[]
@Bean
fun authz(): AuthorizationManagerFactory<Object> {
return DefaultAuthorizationManagerFactory.builder<Object>()
.requireAdditionalAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY).build()
}
// end::authorizationManagerFactoryBean[]
// end::httpSecurity[]
@Bean
fun userDetailsService(): UserDetailsService {
return InMemoryUserDetailsManager(

127
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/selectivemfa/SelectiveMfaConfigurationTests.kt

@ -0,0 +1,127 @@ @@ -0,0 +1,127 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authentication.selectivemfa
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.core.GrantedAuthorities
import org.springframework.security.test.context.support.WithMockUser
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
import org.springframework.test.context.TestExecutionListeners
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
/**
* Tests [CustomX509Configuration].
*
* @author Rob Winch
*/
@ExtendWith(SpringExtension::class, SpringTestContextExtension::class)
@TestExecutionListeners(WithSecurityContextTestExecutionListener::class)
class SelectiveMfaConfigurationTests {
@JvmField
val spring: SpringTestContext = SpringTestContext(this)
@Autowired
var mockMvc: MockMvc? = null
@Test
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, "ROLE_ADMIN"])
@Throws(Exception::class)
fun adminWhenMissingOttThenRequired() {
this.spring.register(
SelectiveMfaConfiguration::class.java, Http200Controller::class.java
).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/admin/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
.andExpect(MockMvcResultMatchers.redirectedUrlPattern("http://localhost/login?*"))
// @formatter:on
}
@Test
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_ADMIN"])
@Throws(
Exception::class
)
fun adminWhenMfaThenAllowed() {
this.spring.register(
SelectiveMfaConfiguration::class.java, Http200Controller::class.java
).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/admin/"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(SecurityMockMvcResultMatchers.authenticated().withUsername("user"))
// @formatter:on
}
@Test
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, "ROLE_ADMIN"])
@Throws(Exception::class)
fun userSettingsRequiresMfa() {
this.spring.register(
SelectiveMfaConfiguration::class.java, Http200Controller::class.java
).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/admin/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
.andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing"))
// @formatter:on
}
@Test
@WithMockUser(authorities = [GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, "ROLE_USER"])
@Throws(Exception::class)
fun userSettingsWhenMissingOttThenRequired() {
this.spring.register(
SelectiveMfaConfiguration::class.java, Http200Controller::class.java
).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/user/settings/"))
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
.andExpect(MockMvcResultMatchers.redirectedUrlPattern("http://localhost/login?*"))
// @formatter:on
}
@Test
@WithMockUser(roles = ["USER"])
@Throws(Exception::class)
fun rootDoesNotRequireMfa() {
this.spring.register(
SelectiveMfaConfiguration::class.java, Http200Controller::class.java
).autowire()
// @formatter:off
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk())
// @formatter:on
}
@RestController
internal class Http200Controller {
@GetMapping("/**")
fun ok(): String {
return "ok"
}
}
}
Loading…
Cancel
Save