diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 6d48be0516..b08451fb32 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -1024,7 +1024,7 @@ public HttpSecurity logout(Customizer> logoutCust * @return the {@link HttpSecurity} for further customizations @ */ public HttpSecurity anonymous(Customizer> anonymousCustomizer) { - anonymousCustomizer.customize(getOrApply(new AnonymousConfigurer<>())); + anonymousCustomizer.customize(getOrApply(new AnonymousConfigurer<>(getContext()))); return HttpSecurity.this; } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java index c38b3485e7..89fc9adcc4 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java @@ -19,12 +19,14 @@ import java.util.List; import java.util.UUID; +import org.springframework.context.ApplicationContext; import org.springframework.security.authentication.AnonymousAuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.SecurityConfigurer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; @@ -40,6 +42,7 @@ * * @author Rob Winch * @author DingHao + * @author Ngoc Nhan * @since 3.2 */ public final class AnonymousConfigurer> @@ -53,15 +56,20 @@ public final class AnonymousConfigurer> private Object principal = "anonymousUser"; - private List authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"); + private List authorities; private String computedKey; /** * Creates a new instance. + * @param context the {@link ApplicationContext} to use * @see HttpSecurity#anonymous(Customizer) */ - public AnonymousConfigurer() { + public AnonymousConfigurer(ApplicationContext context) { + GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBeanProvider(GrantedAuthorityDefaults.class) + .getIfAvailable(); + String rolePrefix = (grantedAuthorityDefaults != null) ? grantedAuthorityDefaults.getRolePrefix() : "ROLE_"; + this.authorities = AuthorityUtils.createAuthorityList(rolePrefix + "ANONYMOUS"); } /** diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.java index b8963fcbd3..6560c432f8 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.java @@ -23,12 +23,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -48,6 +51,7 @@ import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -55,6 +59,7 @@ /** * @author Rob Winch * @author Josh Cummings + * @author Ngoc Nhan */ @ExtendWith(SpringTestContextExtension.class) public class AnonymousConfigurerTests { @@ -116,6 +121,15 @@ public void anonymousAuthenticationWhenUsingAuthenticationDetailsSourceRefThenMa verify(source).buildDetails(any(HttpServletRequest.class)); } + @Test + public void anonymousWithCustomAuthorityDefaults() throws Exception { + this.spring.register(AnonymousGrantedAuthorityDefaultsConfig.class, PrincipalController.class).autowire(); + this.mockMvc.perform(get("/anonymous").with(anonymous())).andExpect(status().isForbidden()); + this.mockMvc.perform(get("/anonymous").with(anonymous().authorities("my_roles_ANONYMOUS"))) + .andExpect(status().isOk()) + .andExpect(content().string("anonymous")); + } + @Configuration @EnableWebSecurity @EnableWebMvc @@ -261,6 +275,31 @@ String principal(@AuthenticationPrincipal String principal) { return principal; } + @GetMapping("/anonymous") + @PreAuthorize("hasRole('ANONYMOUS')") + String getAnonymous(@AuthenticationPrincipal String principal) { + return principal; + } + + } + + @EnableWebMvc + @EnableWebSecurity + @EnableMethodSecurity + @Configuration + static class AnonymousGrantedAuthorityDefaultsConfig { + + @Bean + GrantedAuthorityDefaults grantedAuthorityDefaults() { + return new GrantedAuthorityDefaults("my_roles_"); + } + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests((authorize) -> authorize.anyRequest().anonymous()); + return http.build(); + } + } } diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java index f60860c13f..6caa66872b 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java @@ -117,6 +117,7 @@ * Security. * * @author Rob Winch + * @author Ngoc Nhan * @since 4.0 */ public final class SecurityMockMvcRequestPostProcessors { @@ -346,7 +347,7 @@ public static RequestPostProcessor authentication(Authentication authentication) * * @return the {@link RequestPostProcessor} to use */ - public static RequestPostProcessor anonymous() { + public static AnonymousRequestPostProcessor anonymous() { return new AnonymousRequestPostProcessor(); } @@ -1020,16 +1021,64 @@ private User createUser() { } - private static class AnonymousRequestPostProcessor extends SecurityContextRequestPostProcessorSupport + public static final class AnonymousRequestPostProcessor extends SecurityContextRequestPostProcessorSupport implements RequestPostProcessor { - private AuthenticationRequestPostProcessor delegate = new AuthenticationRequestPostProcessor( - new AnonymousAuthenticationToken("key", "anonymous", - AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"))); + private String key = "key"; + + private Object principal = "anonymous"; + + private Collection authorities = AuthorityUtils + .createAuthorityList("ROLE_ANONYMOUS"); @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - return this.delegate.postProcessRequest(request); + AuthenticationRequestPostProcessor delegate = new AuthenticationRequestPostProcessor( + this.createAnonymous()); + return delegate.postProcessRequest(request); + } + + /** + * Creates a new {@link AnonymousAuthenticationToken}. + * @return the {@link AnonymousAuthenticationToken} for the principal + */ + private AnonymousAuthenticationToken createAnonymous() { + return new AnonymousAuthenticationToken(this.key, this.principal, this.authorities); + } + + /** + * Populates the user's {@link GrantedAuthority}'s. The default is + * {@code ROLE_ANONYMOUS}. + * @param authorities the authorities to grant to the anonymous user + * @return the {@link AnonymousRequestPostProcessor} for further customizations + */ + public AnonymousRequestPostProcessor authorities(String... authorities) { + Assert.notNull(authorities, "authorities cannot be null"); + this.authorities = AuthorityUtils.createAuthorityList(authorities); + return this; + } + + /** + * Sets the key used to identify the anonymous user. + * @param key the key to use + * @return the {@link AnonymousRequestPostProcessor} for further customizations + */ + public AnonymousRequestPostProcessor key(String key) { + Assert.hasLength(key, "key cannot be null or empty"); + this.key = key; + return this; + } + + /** + * Sets the principal for the anonymous user. + * @param principal the principal to use + * @return the {@link AnonymousRequestPostProcessor} for further customizations + */ + public AnonymousRequestPostProcessor principal(Object principal) { + Assert.notNull(principal, "principal cannot be null"); + Assert.isTrue(!"".equals(principal), "principal cannot be empty"); + this.principal = principal; + return this; } }