diff --git a/docs/manual/src/docs/asciidoc/_includes/test.adoc b/docs/manual/src/docs/asciidoc/_includes/test.adoc index c1c994006b..083d3366c0 100644 --- a/docs/manual/src/docs/asciidoc/_includes/test.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/test.adoc @@ -137,6 +137,37 @@ For example, the following would run every test with a user with the username "a public class WithMockUserTests { ---- + +[[test-method-withanonymoususer]] +=== @WithAnonymousUser + +Using `@WithAnonymousUser` allows running as an anonymous user. +This is especially convenient when you wish to run most of your tests with a specific user, but want to run a few tests as an anonymous user. +For example, the following will run withMockUser1 and withMockUser2 using <> and anonymous as an anonymous user. + +[source,java] +---- +@RunWith(SpringJUnit4ClassRunner.class) +@WithMockUser +public class WithUserClassLevelAuthenticationTests { + + @Test + public void withMockUser1() { + } + + @Test + public void withMockUser2() { + } + + @Test + @WithAnonymousUser + public void anonymous() throws Exception { + // override default to run as anonymous user + } +} +---- + + [[test-method-withuserdetails]] === @WithUserDetails @@ -409,6 +440,16 @@ mvc .perform(get("/").with(user(userDetails))) ---- +You can run as anonymous user using the following: + +[source,java] +---- +mvc + .perform(get("/").with(anonymous())) +---- + +This is especially useful if you are running with a default user and wish to execute a few requests as an anonymous user. + If you want a custom `Authentication` (which does not need to exist) you can do so using the following: [source,java] diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index 9cb293fe17..bc971e94a0 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -373,6 +373,7 @@ This will give you access to the entire project history (including all releases ** <> ** <> * <> +* <> === What's new in Spring Security 4.0 diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUser.java b/test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUser.java new file mode 100644 index 0000000000..d9db9c1204 --- /dev/null +++ b/test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUser.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2015 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 + * + * http://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.test.context.support; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; + +/** + * When used with {@link WithSecurityContextTestExecutionListener} this + * annotation can be added to a test method to emulate running with an anonymous + * user. The {@link SecurityContext} that is used will contain an + * {@link AnonymousAuthenticationToken}. This is useful when a user wants to run + * a majority of tests as a specific user and wishes to override a few methods + * to be anonymous. For example: + * + *
+ * 
+ * @WithMockUser
+ * public class SecurityTests {
+ *     @Test
+ *     @WithAnonymousUser
+ *     public void runAsAnonymous() {
+ *         // ... run as an anonymous user ...
+ *     }
+ *
+ *     // ... lots of tests ran with a default user ...
+ * }
+ * 
+ * 
+ * + * @author Rob Winch + * @since 4.1 + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@WithSecurityContext(factory = WithAnonymousUserSecurityContextFactory.class) +public @interface WithAnonymousUser { + +} diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUserSecurityContextFactory.java b/test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUserSecurityContextFactory.java new file mode 100644 index 0000000000..09d7824472 --- /dev/null +++ b/test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUserSecurityContextFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.test.context.support; + +import java.util.List; + +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * A {@link WithAnonymousUserSecurityContextFactory} that runs with an {@link AnonymousAuthenticationToken}. + * . + * + * @see WithUserDetails + * + * @author Rob Winch + * @since 4.1 + */ + +final class WithAnonymousUserSecurityContextFactory implements + WithSecurityContextFactory { + + public SecurityContext createSecurityContext(WithAnonymousUser withUser) { + List authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"); + Authentication authentication = new AnonymousAuthenticationToken("key", "anonymous", authorities); + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(authentication); + return context; + } +} \ No newline at end of file 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 b9fe009776..2249fa49df 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 @@ -34,6 +34,7 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -217,6 +218,38 @@ public final class SecurityMockMvcRequestPostProcessors { return new AuthenticationRequestPostProcessor(authentication); } + /** + * Establish a {@link SecurityContext} that uses an + * {@link AnonymousAuthenticationToken}. This is useful when a user wants to + * run a majority of tests as a specific user and wishes to override a few + * methods to be anonymous. For example: + * + *
+	 * 
+	 * public class SecurityTests {
+	 *     @Before
+	 *     public void setup() {
+	 *         mockMvc = MockMvcBuilders
+	 *             .webAppContextSetup(context)
+	 *             .defaultRequest(get("/").with(user("user")))
+	 *             .build();
+	 *     }
+	 *
+	 *     @Test
+	 *     public void anonymous() {
+	 *         mockMvc.perform(get("anonymous").with(anonymous()));
+	 *     }
+	 *     // ... lots of tests ran with a default user ...
+	 * }
+	 * 
+	 * 
+ * + * @return the {@link RequestPostProcessor} to use + */ + public static RequestPostProcessor anonymous() { + return new AnonymousRequestPostProcessor(); + } + /** * Establish the specified {@link SecurityContext} to be used. * @@ -761,6 +794,17 @@ public final class SecurityMockMvcRequestPostProcessors { } } + private static class AnonymousRequestPostProcessor extends SecurityContextRequestPostProcessorSupport implements RequestPostProcessor { + private AuthenticationRequestPostProcessor delegate = new AuthenticationRequestPostProcessor(new AnonymousAuthenticationToken("key", "anonymous", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"))); + + /* (non-Javadoc) + * @see org.springframework.test.web.servlet.request.RequestPostProcessor#postProcessRequest(org.springframework.mock.web.MockHttpServletRequest) + */ + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + return delegate.postProcessRequest(request); + } + } + private static class HttpBasicRequestPostProcessor implements RequestPostProcessor { private String headerValue; diff --git a/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/DefaultfSecurityRequestsTests.java b/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/DefaultfSecurityRequestsTests.java index 64980d4e2f..24e0ab10dd 100644 --- a/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/DefaultfSecurityRequestsTests.java +++ b/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/DefaultfSecurityRequestsTests.java @@ -18,20 +18,17 @@ package org.springframework.security.test.web.servlet.showcase.secured; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import javax.servlet.Filter; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; 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.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; @@ -48,9 +45,6 @@ public class DefaultfSecurityRequestsTests { @Autowired private WebApplicationContext context; - @Autowired - private Filter springSecurityFilterChain; - private MockMvc mvc; @Before @@ -78,6 +72,15 @@ public class DefaultfSecurityRequestsTests { .andExpect(authenticated().withUsername("user")); } + @Test + public void requestProtectedUrlWithAnonymous() throws Exception { + mvc.perform(get("/admin").with(anonymous())) + // Ensure we got past Security + .andExpect(status().isUnauthorized()) + // Ensure it appears we are authenticated with user + .andExpect(unauthenticated()); + } + @EnableWebSecurity @EnableWebMvc static class Config extends WebSecurityConfigurerAdapter { @@ -90,7 +93,7 @@ public class DefaultfSecurityRequestsTests { .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() - .formLogin(); + .httpBasic(); } // @formatter:on diff --git a/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/WithUserClassLevelAuthenticationTests.java b/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/WithUserClassLevelAuthenticationTests.java index 02bb167bd1..cdfa3726d5 100644 --- a/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/WithUserClassLevelAuthenticationTests.java +++ b/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/WithUserClassLevelAuthenticationTests.java @@ -15,15 +15,21 @@ */ package org.springframework.security.test.web.servlet.showcase.secured; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; 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.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -33,11 +39,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = WithUserClassLevelAuthenticationTests.Config.class) @WebAppConfiguration @@ -72,6 +73,16 @@ public class WithUserClassLevelAuthenticationTests { .andExpect(authenticated().withUsername("user").withRoles("ADMIN")); } + @Test + @WithAnonymousUser + public void requestProtectedUrlWithAnonymous() throws Exception { + mvc.perform(get("/")) + // Ensure did not get past security + .andExpect(status().isUnauthorized()) + // Ensure not authenticated + .andExpect(unauthenticated()); + } + @EnableWebSecurity @EnableWebMvc static class Config extends WebSecurityConfigurerAdapter { @@ -84,7 +95,7 @@ public class WithUserClassLevelAuthenticationTests { .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() - .formLogin(); + .httpBasic(); } // @formatter:on