Skip to content

Commit 6f0029f

Browse files
committed
Add Support for @transient SecurityContext
Closes gh-9995
1 parent 8aa3f29 commit 6f0029f

File tree

3 files changed

+112
-4
lines changed

3 files changed

+112
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.core.context;
18+
19+
import org.springframework.security.core.Authentication;
20+
import org.springframework.security.core.Transient;
21+
22+
/**
23+
* A {@link SecurityContext} that is annotated with @{@link Transient} and thus should
24+
* never be stored across requests. This is useful in situations where one might run as a
25+
* different user for part of a request.
26+
*
27+
* @author Rob Winch
28+
* @since 5.7
29+
*/
30+
@Transient
31+
public class TransientSecurityContext extends SecurityContextImpl {
32+
33+
public TransientSecurityContext() {
34+
}
35+
36+
public TransientSecurityContext(Authentication authentication) {
37+
super(authentication);
38+
}
39+
40+
}

Diff for: web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,11 @@ public void setSpringSecurityContextKey(String springSecurityContextKey) {
232232
this.springSecurityContextKey = springSecurityContextKey;
233233
}
234234

235-
private boolean isTransientAuthentication(Authentication authentication) {
236-
if (authentication == null) {
235+
private boolean isTransient(Object object) {
236+
if (object == null) {
237237
return false;
238238
}
239-
return AnnotationUtils.getAnnotation(authentication.getClass(), Transient.class) != null;
239+
return AnnotationUtils.getAnnotation(object.getClass(), Transient.class) != null;
240240
}
241241

242242
/**
@@ -329,8 +329,11 @@ final class SaveToSessionResponseWrapper extends SaveContextOnUpdateOrErrorRespo
329329
*/
330330
@Override
331331
protected void saveContext(SecurityContext context) {
332+
if (isTransient(context)) {
333+
return;
334+
}
332335
final Authentication authentication = context.getAuthentication();
333-
if (isTransientAuthentication(authentication)) {
336+
if (isTransient(authentication)) {
334337
return;
335338
}
336339
HttpSession httpSession = this.request.getSession(false);

Diff for: web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java

+65
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,16 @@
4343
import org.springframework.security.authentication.AbstractAuthenticationToken;
4444
import org.springframework.security.authentication.AnonymousAuthenticationToken;
4545
import org.springframework.security.authentication.AuthenticationTrustResolver;
46+
import org.springframework.security.authentication.TestAuthentication;
4647
import org.springframework.security.authentication.TestingAuthenticationToken;
4748
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
49+
import org.springframework.security.core.Authentication;
4850
import org.springframework.security.core.Transient;
4951
import org.springframework.security.core.authority.AuthorityUtils;
5052
import org.springframework.security.core.context.SecurityContext;
5153
import org.springframework.security.core.context.SecurityContextHolder;
5254
import org.springframework.security.core.context.SecurityContextImpl;
55+
import org.springframework.security.core.context.TransientSecurityContext;
5356
import org.springframework.security.core.userdetails.User;
5457
import org.springframework.security.core.userdetails.UserDetails;
5558

@@ -587,6 +590,68 @@ public void failsWithStandardResponse() {
587590
assertThatIllegalStateException().isThrownBy(() -> repo.saveContext(context, request, response));
588591
}
589592

593+
@Test
594+
public void saveContextWhenTransientSecurityContextThenSkipped() {
595+
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
596+
MockHttpServletRequest request = new MockHttpServletRequest();
597+
MockHttpServletResponse response = new MockHttpServletResponse();
598+
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
599+
SecurityContext context = repo.loadContext(holder);
600+
SecurityContext transientSecurityContext = new TransientSecurityContext();
601+
Authentication authentication = TestAuthentication.authenticatedUser();
602+
transientSecurityContext.setAuthentication(authentication);
603+
repo.saveContext(transientSecurityContext, holder.getRequest(), holder.getResponse());
604+
MockHttpSession session = (MockHttpSession) request.getSession(false);
605+
assertThat(session).isNull();
606+
}
607+
608+
@Test
609+
public void saveContextWhenTransientSecurityContextSubclassThenSkipped() {
610+
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
611+
MockHttpServletRequest request = new MockHttpServletRequest();
612+
MockHttpServletResponse response = new MockHttpServletResponse();
613+
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
614+
SecurityContext context = repo.loadContext(holder);
615+
SecurityContext transientSecurityContext = new TransientSecurityContext() {
616+
};
617+
Authentication authentication = TestAuthentication.authenticatedUser();
618+
transientSecurityContext.setAuthentication(authentication);
619+
repo.saveContext(transientSecurityContext, holder.getRequest(), holder.getResponse());
620+
MockHttpSession session = (MockHttpSession) request.getSession(false);
621+
assertThat(session).isNull();
622+
}
623+
624+
@Test
625+
public void saveContextWhenTransientSecurityContextAndSessionExistsThenSkipped() {
626+
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
627+
MockHttpServletRequest request = new MockHttpServletRequest();
628+
request.getSession(); // ensure the session exists
629+
MockHttpServletResponse response = new MockHttpServletResponse();
630+
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
631+
SecurityContext context = repo.loadContext(holder);
632+
SecurityContext transientSecurityContext = new TransientSecurityContext();
633+
Authentication authentication = TestAuthentication.authenticatedUser();
634+
transientSecurityContext.setAuthentication(authentication);
635+
repo.saveContext(transientSecurityContext, holder.getRequest(), holder.getResponse());
636+
MockHttpSession session = (MockHttpSession) request.getSession(false);
637+
assertThat(Collections.list(session.getAttributeNames())).isEmpty();
638+
}
639+
640+
@Test
641+
public void saveContextWhenTransientSecurityContextWithCustomAnnotationThenSkipped() {
642+
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
643+
MockHttpServletRequest request = new MockHttpServletRequest();
644+
MockHttpServletResponse response = new MockHttpServletResponse();
645+
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
646+
SecurityContext context = repo.loadContext(holder);
647+
SecurityContext transientSecurityContext = new TransientSecurityContext();
648+
Authentication authentication = TestAuthentication.authenticatedUser();
649+
transientSecurityContext.setAuthentication(authentication);
650+
repo.saveContext(transientSecurityContext, holder.getRequest(), holder.getResponse());
651+
MockHttpSession session = (MockHttpSession) request.getSession(false);
652+
assertThat(session).isNull();
653+
}
654+
590655
@Test
591656
public void saveContextWhenTransientAuthenticationThenSkipped() {
592657
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();

0 commit comments

Comments
 (0)