Skip to content

Commit df7a7cd

Browse files
committed
Update Test for Method Security
Issue spring-projectsgh-17936
1 parent e66c498 commit df7a7cd

File tree

1 file changed

+83
-68
lines changed

1 file changed

+83
-68
lines changed

config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java

Lines changed: 83 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -16,54 +16,52 @@
1616

1717
package org.springframework.security.config.annotation.web.configurers;
1818

19-
import java.util.ArrayList;
20-
import java.util.Collection;
21-
import java.util.List;
22-
import java.util.function.Supplier;
23-
24-
import org.jspecify.annotations.Nullable;
2519
import org.junit.jupiter.api.Test;
2620
import org.junit.jupiter.api.extension.ExtendWith;
2721

2822
import org.springframework.beans.factory.annotation.Autowired;
2923
import org.springframework.context.annotation.Bean;
3024
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.security.access.prepost.PreAuthorize;
3126
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
32-
import org.springframework.security.authorization.AuthorityAuthorizationDecision;
27+
import org.springframework.security.authorization.AllAuthoritiesAuthorizationManager;
28+
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
29+
import org.springframework.security.authorization.AuthorityAuthorizationManager;
30+
import org.springframework.security.authorization.AuthorizationDecision;
3331
import org.springframework.security.authorization.AuthorizationManager;
34-
import org.springframework.security.authorization.AuthorizationResult;
32+
import org.springframework.security.authorization.AuthorizationManagers;
3533
import org.springframework.security.config.Customizer;
3634
import org.springframework.security.config.ObjectPostProcessor;
3735
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
36+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
3837
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3938
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
4039
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
4140
import org.springframework.security.config.test.SpringTestContext;
4241
import org.springframework.security.config.test.SpringTestContextExtension;
4342
import org.springframework.security.config.users.AuthenticationTestConfiguration;
44-
import org.springframework.security.core.Authentication;
45-
import org.springframework.security.core.GrantedAuthority;
46-
import org.springframework.security.core.authority.AuthorityUtils;
4743
import org.springframework.security.core.context.SecurityContextChangedListener;
4844
import org.springframework.security.core.context.SecurityContextHolderStrategy;
4945
import org.springframework.security.core.userdetails.PasswordEncodedUser;
46+
import org.springframework.security.core.userdetails.User;
5047
import org.springframework.security.core.userdetails.UserDetails;
5148
import org.springframework.security.core.userdetails.UserDetailsService;
52-
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
53-
import org.springframework.security.crypto.password.PasswordEncoder;
5449
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
5550
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders;
5651
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
5752
import org.springframework.security.web.PortMapper;
5853
import org.springframework.security.web.SecurityFilterChain;
5954
import org.springframework.security.web.access.ExceptionTranslationFilter;
55+
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
6056
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
6157
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
6258
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
6359
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
6460
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
6561
import org.springframework.security.web.savedrequest.RequestCache;
6662
import org.springframework.test.web.servlet.MockMvc;
63+
import org.springframework.web.bind.annotation.GetMapping;
64+
import org.springframework.web.bind.annotation.RestController;
6765
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
6866

6967
import static org.hamcrest.Matchers.containsString;
@@ -77,6 +75,7 @@
7775
import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
7876
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
7977
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout;
78+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
8079
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
8180
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
8281
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -401,57 +400,58 @@ public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTra
401400

402401
@Test
403402
void requestWhenUnauthenticatedThenRequiresTwoSteps() throws Exception {
404-
this.spring.register(MfaDslConfig.class).autowire();
403+
this.spring.register(MfaDslConfig.class, UserConfig.class).autowire();
405404
UserDetails user = PasswordEncodedUser.user();
406-
this.mockMvc.perform(get("/profile").with(SecurityMockMvcRequestPostProcessors.user(user)))
405+
this.mockMvc.perform(get("/profile").with(user(user)))
407406
.andExpect(status().is3xxRedirection())
408407
.andExpect(redirectedUrl("http://localhost/login"));
409408
this.mockMvc
410-
.perform(post("/ott/generate").param("username", "user")
411-
.with(SecurityMockMvcRequestPostProcessors.user(user))
409+
.perform(post("/ott/generate").param("username", "rod")
410+
.with(user(user))
412411
.with(SecurityMockMvcRequestPostProcessors.csrf()))
413412
.andExpect(status().is3xxRedirection())
414413
.andExpect(redirectedUrl("/ott/sent"));
415414
this.mockMvc
416-
.perform(post("/login").param("username", user.getUsername())
417-
.param("password", user.getPassword())
415+
.perform(post("/login").param("username", "rod")
416+
.param("password", "password")
418417
.with(SecurityMockMvcRequestPostProcessors.csrf()))
419418
.andExpect(status().is3xxRedirection())
420419
.andExpect(redirectedUrl("/"));
421420
user = PasswordEncodedUser.withUserDetails(user).authorities("profile:read", "FACTOR_OTT").build();
422-
this.mockMvc.perform(get("/profile").with(SecurityMockMvcRequestPostProcessors.user(user)))
421+
this.mockMvc.perform(get("/profile").with(user(user)))
423422
.andExpect(status().is3xxRedirection())
424423
.andExpect(redirectedUrl("http://localhost/login"));
425424
user = PasswordEncodedUser.withUserDetails(user).authorities("profile:read", "FACTOR_PASSWORD").build();
426-
this.mockMvc.perform(get("/profile").with(SecurityMockMvcRequestPostProcessors.user(user)))
425+
this.mockMvc.perform(get("/profile").with(user(user)))
427426
.andExpect(status().isOk())
428427
.andExpect(content().string(containsString("/ott/generate")));
429428
user = PasswordEncodedUser.withUserDetails(user)
430429
.authorities("profile:read", "FACTOR_PASSWORD", "FACTOR_OTT")
431430
.build();
432-
this.mockMvc.perform(get("/profile").with(SecurityMockMvcRequestPostProcessors.user(user)))
433-
.andExpect(status().isNotFound());
431+
this.mockMvc.perform(get("/profile").with(user(user))).andExpect(status().isNotFound());
434432
}
435433

436434
@Test
437435
void requestWhenUnauthenticatedX509ThenRequiresTwoSteps() throws Exception {
438-
this.spring.register(MfaDslX509Config.class).autowire();
439-
this.mockMvc.perform(get("/")).andExpect(status().isForbidden());
436+
this.spring.register(MfaDslX509Config.class, UserConfig.class, org.springframework.security.config.annotation.web.configurers.FormLoginConfigurerTests.BasicMfaController.class).autowire();
437+
this.mockMvc.perform(get("/profile")).andExpect(status().isForbidden());
438+
this.mockMvc.perform(get("/profile").with(user(User.withUsername("rod").authorities("profile:read").build())))
439+
.andExpect(status().isForbidden());
440440
this.mockMvc.perform(get("/login")).andExpect(status().isOk());
441-
this.mockMvc.perform(get("/").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer")))
441+
this.mockMvc.perform(get("/profile").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer")))
442442
.andExpect(status().is3xxRedirection())
443443
.andExpect(redirectedUrl("http://localhost/login"));
444-
UserDetails user = PasswordEncodedUser.withUsername("rod")
445-
.password("password")
446-
.authorities("AUTHN_FORM")
447-
.build();
448444
this.mockMvc
449-
.perform(post("/login").param("username", user.getUsername())
450-
.param("password", user.getPassword())
445+
.perform(post("/login").param("username", "rod")
446+
.param("password", "password")
451447
.with(SecurityMockMvcRequestPostProcessors.x509("rod.cer"))
452448
.with(SecurityMockMvcRequestPostProcessors.csrf()))
453449
.andExpect(status().is3xxRedirection())
454450
.andExpect(redirectedUrl("/"));
451+
UserDetails authorized = PasswordEncodedUser.withUsername("rod")
452+
.authorities("profile:read", "FACTOR_X509", "FACTOR_PASSWORD")
453+
.build();
454+
this.mockMvc.perform(get("/profile").with(user(authorized))).andExpect(status().isOk());
455455
}
456456

457457
@Configuration
@@ -795,83 +795,98 @@ public <O> O postProcess(O object) {
795795
static class MfaDslConfig {
796796

797797
@Bean
798-
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
798+
SecurityFilterChain filterChain(HttpSecurity http, AuthorizationManagerFactory<RequestAuthorizationContext> authz) throws Exception {
799799
// @formatter:off
800800
http
801801
.formLogin(Customizer.withDefaults())
802802
.oneTimeTokenLogin(Customizer.withDefaults())
803803
.authorizeHttpRequests((authorize) -> authorize
804-
.requestMatchers("/profile").access(
805-
new HasAllAuthoritiesAuthorizationManager<>("profile:read", "FACTOR_PASSWORD", "FACTOR_OTT")
806-
)
807-
.anyRequest().access(new HasAllAuthoritiesAuthorizationManager<>("FACTOR_PASSWORD", "FACTOR_OTT"))
804+
.requestMatchers("/profile").access(authz.hasAuthority("profile:read"))
805+
.anyRequest().access(authz.authenticated())
808806
);
809807
return http.build();
810808
// @formatter:on
811809
}
812810

813811
@Bean
814-
UserDetailsService users() {
815-
return new InMemoryUserDetailsManager(PasswordEncodedUser.user());
816-
}
817-
818-
@Bean
819-
PasswordEncoder encoder() {
820-
return NoOpPasswordEncoder.getInstance();
812+
OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler() {
813+
return new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
821814
}
822815

823816
@Bean
824-
OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler() {
825-
return new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
817+
AuthorizationManagerFactory<?> authz() {
818+
return new AuthorizationManagerFactory<>("FACTOR_PASSWORD", "FACTOR_OTT");
826819
}
827820

828821
}
829822

830823
@Configuration
831824
@EnableWebSecurity
825+
@EnableMethodSecurity
832826
static class MfaDslX509Config {
833827

834828
@Bean
835-
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
829+
SecurityFilterChain filterChain(HttpSecurity http, AuthorizationManagerFactory<RequestAuthorizationContext> authz) throws Exception {
836830
// @formatter:off
837831
http
838-
.formLogin(Customizer.withDefaults())
839832
.x509(Customizer.withDefaults())
833+
.formLogin(Customizer.withDefaults())
840834
.authorizeHttpRequests((authorize) -> authorize
841-
.anyRequest().access(
842-
new HasAllAuthoritiesAuthorizationManager<>("FACTOR_X509", "FACTOR_PASSWORD")
843-
)
835+
.anyRequest().access(authz.authenticated())
844836
);
845837
return http.build();
846838
// @formatter:on
847839
}
848840

849841
@Bean
850-
UserDetailsService users() {
851-
return new InMemoryUserDetailsManager(
852-
PasswordEncodedUser.withUsername("rod").password("{noop}password").build());
842+
AuthorizationManagerFactory<?> authz() {
843+
return new AuthorizationManagerFactory<>("FACTOR_X509", "FACTOR_PASSWORD");
853844
}
854845

855846
}
856847

857-
private static final class HasAllAuthoritiesAuthorizationManager<C> implements AuthorizationManager<C> {
848+
@Configuration
849+
static class UserConfig {
858850

859-
private final Collection<String> authorities;
851+
@Bean
852+
UserDetails rod() {
853+
return PasswordEncodedUser.withUsername("rod").password("password").build();
854+
}
860855

861-
private HasAllAuthoritiesAuthorizationManager(String... authorities) {
862-
this.authorities = List.of(authorities);
856+
@Bean
857+
UserDetailsService users(UserDetails user) {
858+
return new InMemoryUserDetailsManager(user);
863859
}
864860

865-
@Override
866-
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, C object) {
867-
List<String> authorities = authentication.get()
868-
.getAuthorities()
869-
.stream()
870-
.map(GrantedAuthority::getAuthority)
871-
.toList();
872-
List<String> needed = new ArrayList<>(this.authorities);
873-
needed.removeIf(authorities::contains);
874-
return new AuthorityAuthorizationDecision(needed.isEmpty(), AuthorityUtils.createAuthorityList(needed));
861+
}
862+
863+
@RestController
864+
static class BasicMfaController {
865+
866+
@GetMapping("/profile")
867+
@PreAuthorize("@authz.hasAuthority('profile:read')")
868+
String profile() {
869+
return "profile";
870+
}
871+
872+
}
873+
874+
public static class AuthorizationManagerFactory<T> {
875+
876+
private final AuthorizationManager<T> authorities;
877+
878+
AuthorizationManagerFactory(String... authorities) {
879+
this.authorities = AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities);
880+
}
881+
882+
public AuthorizationManager<T> authenticated() {
883+
AuthenticatedAuthorizationManager<T> authenticated = AuthenticatedAuthorizationManager.authenticated();
884+
return AuthorizationManagers.allOf(new AuthorizationDecision(false), this.authorities, authenticated);
885+
}
886+
887+
public AuthorizationManager<T> hasAuthority(String authority) {
888+
AuthorityAuthorizationManager<T> authorized = AuthorityAuthorizationManager.hasAuthority(authority);
889+
return AuthorizationManagers.allOf(new AuthorizationDecision(false), this.authorities, authorized);
875890
}
876891

877892
}

0 commit comments

Comments
 (0)