diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java
new file mode 100644
index 0000000000..673b59ee83
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-2018 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.config.annotation.web.configurers;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationProvider;
+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.http.SessionCreationPolicy;
+import org.springframework.security.config.test.SpringTestRule;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.TransientAuthentication;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+
+/**
+ * @author Josh Cummings
+ */
+public class SessionManagementConfigurerTransientAuthenticationTests {
+
+ @Autowired
+ MockMvc mvc;
+
+ @Rule
+ public final SpringTestRule spring = new SpringTestRule();
+
+ @Test
+ public void postWhenTransientAuthenticationThenNoSessionCreated()
+ throws Exception {
+
+ this.spring.register(WithTransientAuthenticationConfig.class).autowire();
+ MvcResult result = this.mvc.perform(post("/login")).andReturn();
+ assertThat(result.getRequest().getSession(false)).isNull();
+ }
+
+ @Test
+ public void postWhenTransientAuthenticationThenAlwaysSessionOverrides()
+ throws Exception {
+
+ this.spring.register(AlwaysCreateSessionConfig.class).autowire();
+ MvcResult result = this.mvc.perform(post("/login")).andReturn();
+ assertThat(result.getRequest().getSession(false)).isNotNull();
+ }
+
+ @EnableWebSecurity
+ static class WithTransientAuthenticationConfig extends WebSecurityConfigurerAdapter {
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ super.configure(http);
+
+ http
+ .csrf().disable();
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth
+ .authenticationProvider(new TransientAuthenticationProvider());
+ }
+ }
+
+ @EnableWebSecurity
+ static class AlwaysCreateSessionConfig extends WithTransientAuthenticationConfig {
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
+ }
+ }
+
+ static class TransientAuthenticationProvider implements AuthenticationProvider {
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ return new SomeTransientAuthentication();
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return true;
+ }
+ }
+
+ @TransientAuthentication
+ static class SomeTransientAuthentication extends AbstractAuthenticationToken {
+ SomeTransientAuthentication() {
+ super(null);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return null;
+ }
+ }
+}
diff --git a/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java b/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java
new file mode 100644
index 0000000000..2bf529bb2d
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2018 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.config.http;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.config.test.SpringTestRule;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.TransientAuthentication;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+
+/**
+ * @author Josh Cummings
+ */
+public class SessionManagementConfigTransientAuthenticationTests {
+ private static final String CONFIG_LOCATION_PREFIX =
+ "classpath:org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests";
+
+ @Autowired
+ MockMvc mvc;
+
+ @Rule
+ public final SpringTestRule spring = new SpringTestRule();
+
+ @Test
+ public void postWhenTransientAuthenticationThenNoSessionCreated()
+ throws Exception {
+
+ this.spring.configLocations(this.xml("WithTransientAuthentication")).autowire();
+ MvcResult result = this.mvc.perform(post("/login")).andReturn();
+ assertThat(result.getRequest().getSession(false)).isNull();
+ }
+
+ @Test
+ public void postWhenTransientAuthenticationThenAlwaysSessionOverrides()
+ throws Exception {
+
+ this.spring.configLocations(this.xml("CreateSessionAlwaysWithTransientAuthentication")).autowire();
+ MvcResult result = this.mvc.perform(post("/login")).andReturn();
+ assertThat(result.getRequest().getSession(false)).isNotNull();
+ }
+
+ static class TransientAuthenticationProvider implements AuthenticationProvider {
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ return new SomeTransientAuthentication();
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return true;
+ }
+ }
+
+ @TransientAuthentication
+ static class SomeTransientAuthentication extends AbstractAuthenticationToken {
+ SomeTransientAuthentication() {
+ super(null);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return null;
+ }
+ }
+
+ private String xml(String configName) {
+ return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
+ }
+}
diff --git a/config/src/test/resources/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests-CreateSessionAlwaysWithTransientAuthentication.xml b/config/src/test/resources/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests-CreateSessionAlwaysWithTransientAuthentication.xml
new file mode 100644
index 0000000000..804e3dedd4
--- /dev/null
+++ b/config/src/test/resources/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests-CreateSessionAlwaysWithTransientAuthentication.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/src/test/resources/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests-WithTransientAuthentication.xml b/config/src/test/resources/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests-WithTransientAuthentication.xml
new file mode 100644
index 0000000000..ae66dcbbb0
--- /dev/null
+++ b/config/src/test/resources/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests-WithTransientAuthentication.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/main/java/org/springframework/security/core/TransientAuthentication.java b/core/src/main/java/org/springframework/security/core/TransientAuthentication.java
new file mode 100644
index 0000000000..997ab1dcd4
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/core/TransientAuthentication.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2018 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.core;
+
+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;
+
+/**
+ * A marker for {@link Authentication}s that should never be stored across requests, for example
+ * a bearer token authentication
+ *
+ * @author Josh Cummings
+ * @since 5.1
+ */
+@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface TransientAuthentication {
+}
diff --git a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
index 49f541f9d2..c6deefe877 100644
--- a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
+++ b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
@@ -25,9 +25,12 @@ import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.TransientAuthentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -387,6 +390,10 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
}
private HttpSession createNewSessionIfAllowed(SecurityContext context) {
+ if (isTransientAuthentication(context.getAuthentication())) {
+ return null;
+ }
+
if (httpSessionExistedAtStartOfRequest) {
if (logger.isDebugEnabled()) {
logger.debug("HttpSession is now null, but was not null at start of request; "
@@ -437,6 +444,10 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
}
}
+ private boolean isTransientAuthentication(Authentication authentication) {
+ return AnnotationUtils.getAnnotation(authentication.getClass(), TransientAuthentication.class) != null;
+ }
+
/**
* Sets the {@link AuthenticationTrustResolver} to be used. The default is
* {@link AuthenticationTrustResolverImpl}.
diff --git a/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryServlet25Tests.java b/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryServlet25Tests.java
new file mode 100644
index 0000000000..5f9f4c2812
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryServlet25Tests.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2016 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.web.context;
+
+import javax.servlet.ServletRequest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.util.ClassUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.powermock.api.mockito.PowerMockito.spy;
+import static org.powermock.api.mockito.PowerMockito.when;
+
+/**
+ * @author Luke Taylor
+ * @author Rob Winch
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ ClassUtils.class })
+public class HttpSessionSecurityContextRepositoryServlet25Tests {
+ @Test
+ public void servlet25Compatability() throws Exception {
+ spy(ClassUtils.class);
+ when(ClassUtils.class, "hasMethod", ServletRequest.class, "startAsync",
+ new Class[] {}).thenReturn(false);
+ HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
+ response);
+ repo.loadContext(holder);
+ assertThat(holder.getRequest()).isSameAs(request);
+ }
+}
diff --git a/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java b/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java
index 1b9c027fb0..4a9b0845cf 100644
--- a/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java
+++ b/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java
@@ -16,18 +16,11 @@
package org.springframework.security.web.context;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.mock;
-import static org.powermock.api.mockito.PowerMockito.spy;
-import static org.powermock.api.mockito.PowerMockito.when;
-import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
-
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import javax.servlet.ServletOutputStream;
-import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
@@ -36,25 +29,32 @@ import javax.servlet.http.HttpSession;
import org.junit.After;
import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockHttpSession;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.TransientAuthentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.util.ClassUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.when;
+import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
/**
* @author Luke Taylor
* @author Rob Winch
*/
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({ ClassUtils.class })
public class HttpSessionSecurityContextRepositoryTests {
private final TestingAuthenticationToken testToken = new TestingAuthenticationToken(
@@ -65,20 +65,6 @@ public class HttpSessionSecurityContextRepositoryTests {
SecurityContextHolder.clearContext();
}
- @Test
- public void servlet25Compatability() throws Exception {
- spy(ClassUtils.class);
- when(ClassUtils.class, "hasMethod", ServletRequest.class, "startAsync",
- new Class[] {}).thenReturn(false);
- HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
- MockHttpServletRequest request = new MockHttpServletRequest();
- MockHttpServletResponse response = new MockHttpServletResponse();
- HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
- response);
- repo.loadContext(holder);
- assertThat(holder.getRequest()).isSameAs(request);
- }
-
@Test
public void startAsyncDisablesSaveOnCommit() throws Exception {
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
@@ -633,4 +619,102 @@ public class HttpSessionSecurityContextRepositoryTests {
repo.saveContext(context, request, response);
}
+
+ @Test
+ public void saveContextWhenTransientAuthenticationThenSkipped() {
+ HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
+ response);
+ SecurityContext context = repo.loadContext(holder);
+
+ SomeTransientAuthentication authentication = new SomeTransientAuthentication();
+ context.setAuthentication(authentication);
+
+ repo.saveContext(context, holder.getRequest(), holder.getResponse());
+
+ MockHttpSession session = (MockHttpSession) request.getSession(false);
+ assertThat(session).isNull();
+ }
+
+ @Test
+ public void saveContextWhenTransientAuthenticationSubclassThenSkipped() {
+ HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
+ response);
+ SecurityContext context = repo.loadContext(holder);
+
+ SomeTransientAuthenticationSubclass authentication = new SomeTransientAuthenticationSubclass();
+ context.setAuthentication(authentication);
+
+ repo.saveContext(context, holder.getRequest(), holder.getResponse());
+
+ MockHttpSession session = (MockHttpSession) request.getSession(false);
+ assertThat(session).isNull();
+ }
+
+ @Test
+ public void saveContextWhenTransientAuthenticationWithCustomAnnotationThenSkipped() {
+ HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
+ response);
+ SecurityContext context = repo.loadContext(holder);
+
+ SomeOtherTransientAuthentication authentication = new SomeOtherTransientAuthentication();
+ context.setAuthentication(authentication);
+
+ repo.saveContext(context, holder.getRequest(), holder.getResponse());
+
+ MockHttpSession session = (MockHttpSession) request.getSession(false);
+ assertThat(session).isNull();
+ }
+
+ @TransientAuthentication
+ private static class SomeTransientAuthentication extends AbstractAuthenticationToken {
+ public SomeTransientAuthentication() {
+ super(null);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return null;
+ }
+ }
+
+ private static class SomeTransientAuthenticationSubclass extends SomeTransientAuthentication {
+
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @TransientAuthentication
+ public @interface TestTransientAuthentication {
+ }
+
+ @TestTransientAuthentication
+ private static class SomeOtherTransientAuthentication extends AbstractAuthenticationToken {
+ public SomeOtherTransientAuthentication() {
+ super(null);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return null;
+ }
+ }
}