From ae8e12f049876c7d8f8713adb0f12bf742a103bd Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 4 Jul 2019 15:08:00 -0400 Subject: [PATCH] Allow configuration of anonymous through nested builder Issue: gh-5557 --- .../annotation/web/builders/HttpSecurity.java | 70 ++++++++++++ .../configurers/AnonymousConfigurerTests.java | 106 ++++++++++++++++-- 2 files changed, 168 insertions(+), 8 deletions(-) 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 cf31cfeadc..db0f24bede 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 @@ -1531,6 +1531,76 @@ public final class HttpSecurity extends return getOrApply(new AnonymousConfigurer<>()); } + /** + * Allows configuring how an anonymous user is represented. This is automatically + * applied when used in conjunction with {@link WebSecurityConfigurerAdapter}. By + * default anonymous users will be represented with an + * {@link org.springframework.security.authentication.AnonymousAuthenticationToken} + * and contain the role "ROLE_ANONYMOUS". + * + *

Example Configuration

+ * + * The following configuration demonstrates how to specify that anonymous users should + * contain the role "ROLE_ANON" instead. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class AnononymousSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(withDefaults())
+	 * 			// sample anonymous customization
+	 * 			.anonymous(anonymous ->
+	 * 				anonymous
+	 * 					.authorities("ROLE_ANON")
+	 * 			)
+	 * 	}
+	 * }
+	 * 
+ * + * The following demonstrates how to represent anonymous users as null. Note that this + * can cause {@link NullPointerException} in code that assumes anonymous + * authentication is enabled. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class AnonymousSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.formLogin(withDefaults())
+	 * 			// sample anonymous customization
+	 * 			.anonymous(anonymous ->
+	 * 				anonymous.disabled()
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @param anonymousCustomizer the {@link Customizer} to provide more options for + * the {@link AnonymousConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity anonymous(Customizer> anonymousCustomizer) throws Exception { + anonymousCustomizer.customize(getOrApply(new AnonymousConfigurer<>())); + return HttpSecurity.this; + } + + /** * Specifies to support form based authentication. If * {@link FormLoginConfigurer#loginPage(String)} is not specified a default login page 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 b10dcdbe59..11d80e02d8 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -18,18 +18,22 @@ package org.springframework.security.config.annotation.web.configurers; import org.junit.Rule; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import static org.springframework.security.config.Customizer.withDefaults; 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; /** * @author Rob Winch @@ -44,7 +48,7 @@ public class AnonymousConfigurerTests { @Test public void requestWhenAnonymousTwiceInvokedThenDoesNotOverride() throws Exception { - this.spring.register(InvokeTwiceDoesNotOverride.class).autowire(); + this.spring.register(InvokeTwiceDoesNotOverride.class, PrincipalController.class).autowire(); this.mockMvc.perform(get("/")) .andExpect(content().string("principal")); @@ -63,13 +67,99 @@ public class AnonymousConfigurerTests { .and() .anonymous(); } + } - @RestController - static class PrincipalController { - @GetMapping("/") - String principal(@AuthenticationPrincipal String principal) { - return principal; - } + @Test + public void requestWhenAnonymousPrincipalInLambdaThenPrincipalUsed() throws Exception { + this.spring.register(AnonymousPrincipalInLambdaConfig.class, PrincipalController.class).autowire(); + + this.mockMvc.perform(get("/")) + .andExpect(content().string("principal")); + } + + @EnableWebSecurity + @EnableWebMvc + static class AnonymousPrincipalInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .anonymous(anonymous -> + anonymous + .principal("principal") + ); + // @formatter:on + } + } + + @Test + public void requestWhenAnonymousDisabledInLambdaThenRespondsWithForbidden() throws Exception { + this.spring.register(AnonymousDisabledInLambdaConfig.class, PrincipalController.class).autowire(); + + this.mockMvc.perform(get("/")) + .andExpect(status().isForbidden()); + } + + @EnableWebSecurity + static class AnonymousDisabledInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().permitAll() + ) + .anonymous(AbstractHttpConfigurer::disable); + // @formatter:on + } + + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + + @Test + public void requestWhenAnonymousWithDefaultsInLambdaThenRespondsWithOk() throws Exception { + this.spring.register(AnonymousWithDefaultsInLambdaConfig.class, PrincipalController.class).autowire(); + + this.mockMvc.perform(get("/")) + .andExpect(status().isOk()); + } + + @EnableWebSecurity + static class AnonymousWithDefaultsInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().permitAll() + ) + .anonymous(withDefaults()); + // @formatter:on + } + + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + + @RestController + static class PrincipalController { + @GetMapping("/") + String principal(@AuthenticationPrincipal String principal) { + return principal; } } }