Skip to content

Commit ad6fe4f

Browse files
committed
Polish MFA Samples
This commit removes unneeded AuthorizationManagerFactory implementations, simplifies the custom AuthorizationManagerFactory example, and updates usage of hasAllAuthorities. Issue gh-17934
1 parent f652920 commit ad6fe4f

File tree

8 files changed

+78
-269
lines changed

8 files changed

+78
-269
lines changed

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@
1313
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
1414
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
1515

16-
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
17-
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
18-
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
19-
2016
@EnableWebSecurity
2117
@Configuration(proxyBeanMethods = false)
2218
public class ListAuthoritiesEverywhereConfiguration {
@@ -27,8 +23,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
2723
// @formatter:off
2824
http
2925
.authorizeHttpRequests((authorize) -> authorize
30-
.requestMatchers("/admin/**").access(allOf(hasAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY), hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY), hasRole("ADMIN"))) // <1>
31-
.anyRequest().access(allOf(hasAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY), hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY)))
26+
.requestMatchers("/admin/**").hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_ADMIN") // <1>
27+
.anyRequest().hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY)
3228
)
3329
.formLogin(Customizer.withDefaults())
3430
.oneTimeTokenLogin(Customizer.withDefaults());

docs/src/test/java/org/springframework/security/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactory.java

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
package org.springframework.security.docs.servlet.authentication.customauthorizationmanagerfactory;
22

3-
import java.util.Collection;
43
import java.util.function.Supplier;
54

6-
import org.jspecify.annotations.NullMarked;
75
import org.jspecify.annotations.Nullable;
86

97
import org.springframework.context.annotation.Bean;
108
import org.springframework.context.annotation.Configuration;
11-
import org.springframework.security.access.expression.SecurityExpressionOperations;
12-
import org.springframework.security.access.expression.SecurityExpressionRoot;
13-
import org.springframework.security.authorization.AuthorityAuthorizationDecision;
9+
import org.springframework.security.authorization.AuthorityAuthorizationManager;
1410
import org.springframework.security.authorization.AuthorizationDecision;
1511
import org.springframework.security.authorization.AuthorizationManager;
1612
import org.springframework.security.authorization.AuthorizationManagerFactory;
@@ -21,10 +17,9 @@
2117
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
2218
import org.springframework.security.core.Authentication;
2319
import org.springframework.security.core.GrantedAuthorities;
24-
import org.springframework.security.core.GrantedAuthority;
25-
import org.springframework.security.core.authority.AuthorityUtils;
26-
import org.springframework.security.core.userdetails.UserDetails;
20+
import org.springframework.security.core.userdetails.PasswordEncodedUser;
2721
import org.springframework.security.core.userdetails.UserDetailsService;
22+
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
2823
import org.springframework.security.web.SecurityFilterChain;
2924
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
3025
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
@@ -51,50 +46,31 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
5146

5247
// tag::authorizationManager[]
5348
@Component
54-
class OptInToMfaAuthorizationManager implements AuthorizationManager<Object> {
49+
class UserBasedOttAuthorizationManager implements AuthorizationManager<Object> {
5550
@Override
5651
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, Object context) {
57-
MyPrincipal principal = (MyPrincipal) authentication.get().getPrincipal();
58-
if (principal.optedIn()) {
59-
SecurityExpressionOperations sec = new SecurityExpressionRoot<>(authentication, context) {};
60-
return new AuthorityAuthorizationDecision(sec.hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY),
61-
AuthorityUtils.createAuthorityList(GrantedAuthorities.FACTOR_OTT_AUTHORITY));
52+
if ("admin".equals(authentication.get().getName())) {
53+
return AuthorityAuthorizationManager.hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY)
54+
.authorize(authentication, context);
55+
} else {
56+
return new AuthorizationDecision(true);
6257
}
63-
return new AuthorizationDecision(true);
6458
}
6559
}
6660
// end::authorizationManager[]
6761

6862
// tag::authorizationManagerFactory[]
6963
@Bean
70-
AuthorizationManagerFactory<Object> authorizationManagerFactory(OptInToMfaAuthorizationManager optIn) {
64+
AuthorizationManagerFactory<Object> authorizationManagerFactory(UserBasedOttAuthorizationManager optIn) {
7165
DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
7266
defaults.setAdditionalAuthorization(optIn);
7367
return defaults;
7468
}
7569
// end::authorizationManagerFactory[]
7670

77-
@NullMarked
78-
record MyPrincipal(String username, boolean optedIn) implements UserDetails {
79-
@Override
80-
public Collection<? extends GrantedAuthority> getAuthorities() {
81-
return AuthorityUtils.createAuthorityList("app");
82-
}
83-
84-
@Override
85-
public @Nullable String getPassword() {
86-
return null;
87-
}
88-
89-
@Override
90-
public String getUsername() {
91-
return this.username;
92-
}
93-
}
94-
9571
@Bean
96-
UserDetailsService users() {
97-
return (username) -> new MyPrincipal(username, username.equals("optedin"));
72+
public UserDetailsService users() {
73+
return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin());
9874
}
9975

10076
@Bean

docs/src/test/java/org/springframework/security/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.java

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,18 @@
2020
import org.junit.jupiter.api.extension.ExtendWith;
2121

2222
import org.springframework.beans.factory.annotation.Autowired;
23-
import org.springframework.security.authentication.TestingAuthenticationToken;
2423
import org.springframework.security.config.test.SpringTestContext;
2524
import org.springframework.security.config.test.SpringTestContextExtension;
2625
import org.springframework.security.core.GrantedAuthorities;
27-
import org.springframework.security.core.userdetails.UserDetails;
28-
import org.springframework.security.core.userdetails.UserDetailsService;
2926
import org.springframework.security.docs.servlet.authentication.servletx509config.CustomX509Configuration;
27+
import org.springframework.security.test.context.support.WithMockUser;
28+
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
29+
import org.springframework.test.context.TestExecutionListeners;
30+
import org.springframework.test.context.junit.jupiter.SpringExtension;
3031
import org.springframework.test.web.servlet.MockMvc;
3132
import org.springframework.web.bind.annotation.GetMapping;
3233
import org.springframework.web.bind.annotation.RestController;
3334

34-
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
35-
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
3635
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
3736
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
3837
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
@@ -43,48 +42,45 @@
4342
*
4443
* @author Rob Winch
4544
*/
46-
@ExtendWith(SpringTestContextExtension.class)
45+
@ExtendWith({SpringExtension.class, SpringTestContextExtension.class})
46+
@TestExecutionListeners(WithSecurityContextTestExecutionListener.class)
4747
public class CustomAuthorizationManagerFactoryTests {
4848

4949
public final SpringTestContext spring = new SpringTestContext(this);
5050

5151
@Autowired
5252
MockMvc mockMvc;
5353

54-
@Autowired
55-
UserDetailsService users;
56-
5754
@Test
58-
void getWhenOptedInThenRedirectsToOtt() throws Exception {
55+
@WithMockUser(username = "admin")
56+
void getWhenAdminThenRedirectsToOtt() throws Exception {
5957
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
60-
UserDetails user = this.users.loadUserByUsername("optedin");
6158
// @formatter:off
62-
this.mockMvc.perform(get("/").with(user(user)))
59+
this.mockMvc.perform(get("/"))
6360
.andExpect(status().is3xxRedirection())
6461
.andExpect(redirectedUrl("http://localhost/login?factor=ott"));
6562
// @formatter:on
6663
}
6764

6865
@Test
69-
void getWhenNotOptedInThenAllows() throws Exception {
66+
@WithMockUser
67+
void getWhenNotAdminThenAllows() throws Exception {
7068
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
71-
UserDetails user = this.users.loadUserByUsername("user");
7269
// @formatter:off
73-
this.mockMvc.perform(get("/").with(user(user)))
70+
this.mockMvc.perform(get("/"))
7471
.andExpect(status().isOk())
7572
.andExpect(authenticated().withUsername("user"));
7673
// @formatter:on
7774
}
7875

7976
@Test
80-
void getWhenOptedAndHasFactorThenAllows() throws Exception {
77+
@WithMockUser(username = "admin", authorities = GrantedAuthorities.FACTOR_OTT_AUTHORITY)
78+
void getWhenAdminAndHasFactorThenAllows() throws Exception {
8179
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
82-
UserDetails user = this.users.loadUserByUsername("optedin");
83-
TestingAuthenticationToken token = new TestingAuthenticationToken(user, "", GrantedAuthorities.FACTOR_OTT_AUTHORITY);
8480
// @formatter:off
85-
this.mockMvc.perform(get("/").with(authentication(token)))
81+
this.mockMvc.perform(get("/"))
8682
.andExpect(status().isOk())
87-
.andExpect(authenticated().withUsername("optedin"));
83+
.andExpect(authenticated().withUsername("admin"));
8884
// @formatter:on
8985
}
9086

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@
1313
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
1414
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
1515

16-
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
17-
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
18-
1916
@EnableWebSecurity
2017
@Configuration(proxyBeanMethods = false)
2118
class ListAuthoritiesConfiguration {
@@ -26,7 +23,7 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
2623
// @formatter:off
2724
http
2825
.authorizeHttpRequests((authorize) -> authorize
29-
.anyRequest().access(allOf(hasAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY), hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY))) // <1>
26+
.anyRequest().hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY) // <1>
3027
)
3128
.formLogin(Customizer.withDefaults())
3229
.oneTimeTokenLogin(Customizer.withDefaults());
@@ -36,7 +33,7 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
3633
// end::httpSecurity[]
3734

3835
@Bean
39-
UserDetailsService userDetailsService() {
36+
UserDetailsService users() {
4037
return new InMemoryUserDetailsManager(
4138
User.withDefaultPasswordEncoder()
4239
.username("user")

docs/src/test/java/org/springframework/security/docs/servlet/authentication/obtainingmoreauthorization/MissingAuthorityConfiguration.java

Lines changed: 4 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88

99
import org.springframework.context.annotation.Bean;
1010
import org.springframework.context.annotation.Configuration;
11-
import org.springframework.security.authorization.AuthorizationDecision;
12-
import org.springframework.security.authorization.AuthorizationManager;
1311
import org.springframework.security.authorization.AuthorizationManagerFactory;
1412
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
1513
import org.springframework.security.config.Customizer;
@@ -22,12 +20,8 @@
2220
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
2321
import org.springframework.security.web.AuthenticationEntryPoint;
2422
import org.springframework.security.web.SecurityFilterChain;
25-
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
2623
import org.springframework.stereotype.Component;
2724

28-
import static org.springframework.security.authorization.AllAuthoritiesAuthorizationManager.hasAllAuthorities;
29-
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
30-
3125
@EnableWebSecurity
3226
@Configuration(proxyBeanMethods = false)
3327
class MissingAuthorityConfiguration {
@@ -53,83 +47,13 @@ SecurityFilterChain securityFilterChain(HttpSecurity http, ScopeRetrievingAuthen
5347

5448
// tag::authorizationManagerFactoryBean[]
5549
@Bean
56-
AuthorizationManagerFactory<RequestAuthorizationContext> authz() {
57-
return new FactorAuthorizationManagerFactory(hasAllAuthorities(GrantedAuthorities.FACTOR_X509_AUTHORITY, GrantedAuthorities.FACTOR_AUTHORIZATION_CODE_AUTHORITY));
50+
AuthorizationManagerFactory<Object> authz() {
51+
return DefaultAuthorizationManagerFactory.builder()
52+
.requireAdditionalAuthorities(GrantedAuthorities.FACTOR_X509_AUTHORITY, GrantedAuthorities.FACTOR_AUTHORIZATION_CODE_AUTHORITY)
53+
.build();
5854
}
5955
// end::authorizationManagerFactoryBean[]
6056

61-
// tag::authorizationManagerFactory[]
62-
class FactorAuthorizationManagerFactory implements AuthorizationManagerFactory<RequestAuthorizationContext> {
63-
private final AuthorizationManager<RequestAuthorizationContext> hasAuthorities;
64-
private final DefaultAuthorizationManagerFactory<RequestAuthorizationContext> delegate =
65-
new DefaultAuthorizationManagerFactory<>();
66-
67-
FactorAuthorizationManagerFactory(AuthorizationManager<RequestAuthorizationContext> hasAuthorities) {
68-
this.hasAuthorities = hasAuthorities;
69-
}
70-
71-
@Override
72-
public AuthorizationManager<RequestAuthorizationContext> permitAll() {
73-
return this.delegate.permitAll();
74-
}
75-
76-
@Override
77-
public AuthorizationManager<RequestAuthorizationContext> denyAll() {
78-
return this.delegate.denyAll();
79-
}
80-
81-
@Override
82-
public AuthorizationManager<RequestAuthorizationContext> hasRole(String role) {
83-
return hasAnyRole(role);
84-
}
85-
86-
@Override
87-
public AuthorizationManager<RequestAuthorizationContext> hasAnyRole(String... roles) {
88-
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.hasAnyRole(roles));
89-
}
90-
91-
@Override
92-
public AuthorizationManager<RequestAuthorizationContext> hasAllRoles(String... roles) {
93-
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.hasAllRoles(roles));
94-
}
95-
96-
@Override
97-
public AuthorizationManager<RequestAuthorizationContext> hasAuthority(String authority) {
98-
return hasAnyAuthority(authority);
99-
}
100-
101-
@Override
102-
public AuthorizationManager<RequestAuthorizationContext> hasAnyAuthority(String... authorities) {
103-
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.hasAnyAuthority(authorities));
104-
}
105-
106-
@Override
107-
public AuthorizationManager<RequestAuthorizationContext> hasAllAuthorities(String... authorities) {
108-
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.hasAllAuthorities(authorities));
109-
}
110-
111-
@Override
112-
public AuthorizationManager<RequestAuthorizationContext> authenticated() {
113-
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.authenticated());
114-
}
115-
116-
@Override
117-
public AuthorizationManager<RequestAuthorizationContext> fullyAuthenticated() {
118-
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.fullyAuthenticated());
119-
}
120-
121-
@Override
122-
public AuthorizationManager<RequestAuthorizationContext> rememberMe() {
123-
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.rememberMe());
124-
}
125-
126-
@Override
127-
public AuthorizationManager<RequestAuthorizationContext> anonymous() {
128-
return this.delegate.anonymous();
129-
}
130-
}
131-
// end::authorizationManagerFactory[]
132-
13357
// tag::authenticationEntryPoint[]
13458
@Component
13559
class ScopeRetrievingAuthenticationEntryPoint implements AuthenticationEntryPoint {

0 commit comments

Comments
 (0)