Skip to content

Commit c75ca10

Browse files
author
Steve Riesenberg
committed
Add DeferredSecurityContext
Issue gh-12023
1 parent cfb7c87 commit c75ca10

File tree

11 files changed

+190
-23
lines changed

11 files changed

+190
-23
lines changed

config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import org.springframework.security.core.GrantedAuthority;
7676
import org.springframework.security.core.annotation.AuthenticationPrincipal;
7777
import org.springframework.security.core.authority.AuthorityUtils;
78+
import org.springframework.security.core.context.DeferredSecurityContext;
7879
import org.springframework.security.core.context.SecurityContext;
7980
import org.springframework.security.core.context.SecurityContextHolder;
8081
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -490,7 +491,8 @@ public void getWhenExplicitSaveAndRepositoryAndAuthenticatingThenConsultsCustomS
490491
this.spring.configLocations(xml("ExplicitSaveAndExplicitRepository")).autowire();
491492
SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class);
492493
SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "password"));
493-
given(repository.loadContext(any(HttpServletRequest.class))).willReturn(() -> context);
494+
given(repository.loadDeferredContext(any(HttpServletRequest.class)))
495+
.willReturn(new TestDeferredSecurityContext(context, false));
494496
// @formatter:off
495497
MvcResult result = this.mvc.perform(formLogin())
496498
.andExpect(status().is3xxRedirection())
@@ -1044,4 +1046,27 @@ public String encodeRedirectUrl(String url) {
10441046

10451047
}
10461048

1049+
static class TestDeferredSecurityContext implements DeferredSecurityContext {
1050+
1051+
private SecurityContext securityContext;
1052+
1053+
private boolean isGenerated;
1054+
1055+
TestDeferredSecurityContext(SecurityContext securityContext, boolean isGenerated) {
1056+
this.securityContext = securityContext;
1057+
this.isGenerated = isGenerated;
1058+
}
1059+
1060+
@Override
1061+
public SecurityContext get() {
1062+
return this.securityContext;
1063+
}
1064+
1065+
@Override
1066+
public boolean isGenerated() {
1067+
return this.isGenerated;
1068+
}
1069+
1070+
}
1071+
10471072
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 java.util.function.Supplier;
20+
21+
/**
22+
* An interface that allows delayed access to a {@link SecurityContext} that may be
23+
* generated.
24+
*
25+
* @author Steve Riesenberg
26+
* @since 5.8
27+
*/
28+
public interface DeferredSecurityContext extends Supplier<SecurityContext> {
29+
30+
/**
31+
* Returns true if {@link #get()} refers to a generated {@link SecurityContext} or
32+
* false if it already existed.
33+
* @return true if {@link #get()} refers to a generated {@link SecurityContext} or
34+
* false if it already existed
35+
*/
36+
boolean isGenerated();
37+
38+
}

web/src/main/java/org/springframework/security/web/context/HttpRequestResponseHolder.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
*
2828
* @author Luke Taylor
2929
* @since 3.0
30-
* @deprecated Use {@link SecurityContextRepository#loadContext(HttpServletRequest)}
30+
* @deprecated Use
31+
* {@link SecurityContextRepository#loadDeferredContext(HttpServletRequest)}
3132
*/
3233
@Deprecated
3334
public final class HttpRequestResponseHolder {

web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java

+9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.security.web.context;
1818

19+
import java.util.function.Supplier;
20+
1921
import javax.servlet.AsyncContext;
2022
import javax.servlet.ServletRequest;
2123
import javax.servlet.ServletResponse;
@@ -33,6 +35,7 @@
3335
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
3436
import org.springframework.security.core.Authentication;
3537
import org.springframework.security.core.Transient;
38+
import org.springframework.security.core.context.DeferredSecurityContext;
3639
import org.springframework.security.core.context.SecurityContext;
3740
import org.springframework.security.core.context.SecurityContextHolder;
3841
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -136,6 +139,12 @@ public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHold
136139
return context;
137140
}
138141

142+
@Override
143+
public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
144+
Supplier<SecurityContext> supplier = () -> readSecurityContextFromSession(request.getSession(false));
145+
return new SupplierDeferredSecurityContext(supplier, this.securityContextHolderStrategy);
146+
}
147+
139148
@Override
140149
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
141150
SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils.getNativeResponse(response,

web/src/main/java/org/springframework/security/web/context/RequestAttributeSecurityContextRepository.java

+5-8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import javax.servlet.http.HttpServletRequest;
2222
import javax.servlet.http.HttpServletResponse;
2323

24+
import org.springframework.security.core.context.DeferredSecurityContext;
2425
import org.springframework.security.core.context.SecurityContext;
2526
import org.springframework.security.core.context.SecurityContextHolder;
2627
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -76,17 +77,13 @@ public boolean containsContext(HttpServletRequest request) {
7677

7778
@Override
7879
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
79-
return getContextOrEmpty(requestResponseHolder.getRequest());
80+
return loadDeferredContext(requestResponseHolder.getRequest()).get();
8081
}
8182

8283
@Override
83-
public Supplier<SecurityContext> loadContext(HttpServletRequest request) {
84-
return () -> getContextOrEmpty(request);
85-
}
86-
87-
private SecurityContext getContextOrEmpty(HttpServletRequest request) {
88-
SecurityContext context = getContext(request);
89-
return (context != null) ? context : this.securityContextHolderStrategy.createEmptyContext();
84+
public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
85+
Supplier<SecurityContext> supplier = () -> getContext(request);
86+
return new SupplierDeferredSecurityContext(supplier, this.securityContextHolderStrategy);
9087
}
9188

9289
private SecurityContext getContext(HttpServletRequest request) {

web/src/main/java/org/springframework/security/web/context/SaveContextOnUpdateOrErrorResponseWrapper.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
* @author Marten Algesten
4343
* @author Rob Winch
4444
* @since 3.0
45-
* @deprecated Use {@link SecurityContextRepository#loadContext(HttpServletRequest)}
46-
* instead.
45+
* @deprecated Use
46+
* {@link SecurityContextRepository#loadDeferredContext(HttpServletRequest)} instead.
4747
*/
4848
@Deprecated
4949
public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends OnCommittedResponseWrapper {

web/src/main/java/org/springframework/security/web/context/SecurityContextHolderFilter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public SecurityContextHolderFilter(SecurityContextRepository securityContextRepo
6363
@Override
6464
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
6565
throws ServletException, IOException {
66-
Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadContext(request);
66+
Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
6767
try {
6868
this.securityContextHolderStrategy.setDeferredContext(deferredContext);
6969
filterChain.doFilter(request, response);

web/src/main/java/org/springframework/security/web/context/SecurityContextRepository.java

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,7 +21,9 @@
2121
import javax.servlet.http.HttpServletRequest;
2222
import javax.servlet.http.HttpServletResponse;
2323

24+
import org.springframework.security.core.context.DeferredSecurityContext;
2425
import org.springframework.security.core.context.SecurityContext;
26+
import org.springframework.security.core.context.SecurityContextHolder;
2527
import org.springframework.util.function.SingletonSupplier;
2628

2729
/**
@@ -61,7 +63,7 @@ public interface SecurityContextRepository {
6163
* the context should be loaded.
6264
* @return The security context which should be used for the current request, never
6365
* null.
64-
* @deprecated Use {@link #loadContext(HttpServletRequest)} instead.
66+
* @deprecated Use {@link #loadDeferredContext(HttpServletRequest)} instead.
6567
*/
6668
@Deprecated
6769
SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
@@ -75,9 +77,27 @@ public interface SecurityContextRepository {
7577
* @return a {@link Supplier} that returns the {@link SecurityContext} which cannot be
7678
* null.
7779
* @since 5.7
80+
* @deprecated Use
81+
* {@link SecurityContextRepository#loadDeferredContext(HttpServletRequest)} instead
7882
*/
83+
@Deprecated
7984
default Supplier<SecurityContext> loadContext(HttpServletRequest request) {
80-
return SingletonSupplier.of(() -> loadContext(new HttpRequestResponseHolder(request, null)));
85+
return loadDeferredContext(request);
86+
}
87+
88+
/**
89+
* Defers loading the {@link SecurityContext} using the {@link HttpServletRequest}
90+
* until it is needed by the application.
91+
* @param request the {@link HttpServletRequest} to load the {@link SecurityContext}
92+
* from
93+
* @return a {@link DeferredSecurityContext} that returns the {@link SecurityContext}
94+
* which cannot be null
95+
* @since 5.8
96+
*/
97+
default DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
98+
Supplier<SecurityContext> supplier = () -> loadContext(new HttpRequestResponseHolder(request, null));
99+
return new SupplierDeferredSecurityContext(SingletonSupplier.of(supplier),
100+
SecurityContextHolder.getContextHolderStrategy());
81101
}
82102

83103
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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.web.context;
18+
19+
import java.util.function.Supplier;
20+
21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
24+
import org.springframework.core.log.LogMessage;
25+
import org.springframework.security.core.context.DeferredSecurityContext;
26+
import org.springframework.security.core.context.SecurityContext;
27+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
28+
29+
/**
30+
* @author Steve Riesenberg
31+
* @since 5.8
32+
*/
33+
final class SupplierDeferredSecurityContext implements DeferredSecurityContext {
34+
35+
private static final Log logger = LogFactory.getLog(SupplierDeferredSecurityContext.class);
36+
37+
private final Supplier<SecurityContext> supplier;
38+
39+
private final SecurityContextHolderStrategy strategy;
40+
41+
private SecurityContext securityContext;
42+
43+
private boolean missingContext;
44+
45+
SupplierDeferredSecurityContext(Supplier<SecurityContext> supplier, SecurityContextHolderStrategy strategy) {
46+
this.supplier = supplier;
47+
this.strategy = strategy;
48+
}
49+
50+
@Override
51+
public SecurityContext get() {
52+
init();
53+
return this.securityContext;
54+
}
55+
56+
@Override
57+
public boolean isGenerated() {
58+
init();
59+
return this.missingContext;
60+
}
61+
62+
private void init() {
63+
if (this.securityContext != null) {
64+
return;
65+
}
66+
67+
this.securityContext = this.supplier.get();
68+
this.missingContext = (this.securityContext == null);
69+
if (this.missingContext) {
70+
this.securityContext = this.strategy.createEmptyContext();
71+
if (logger.isTraceEnabled()) {
72+
logger.trace(LogMessage.format("Created %s", this.securityContext));
73+
}
74+
}
75+
}
76+
77+
}

web/src/test/java/org/springframework/security/web/context/SecurityContextHolderFilterTests.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ void cleanup() {
7676
void doFilterThenSetsAndClearsSecurityContextHolder() throws Exception {
7777
Authentication authentication = TestAuthentication.authenticatedUser();
7878
SecurityContext expectedContext = new SecurityContextImpl(authentication);
79-
given(this.repository.loadContext(this.requestArg.capture())).willReturn(() -> expectedContext);
79+
given(this.repository.loadDeferredContext(this.requestArg.capture()))
80+
.willReturn(new SupplierDeferredSecurityContext(() -> expectedContext, this.strategy));
8081
FilterChain filterChain = (request, response) -> assertThat(SecurityContextHolder.getContext())
8182
.isEqualTo(expectedContext);
8283

@@ -89,7 +90,8 @@ void doFilterThenSetsAndClearsSecurityContextHolder() throws Exception {
8990
void doFilterThenSetsAndClearsSecurityContextHolderStrategy() throws Exception {
9091
Authentication authentication = TestAuthentication.authenticatedUser();
9192
SecurityContext expectedContext = new SecurityContextImpl(authentication);
92-
given(this.repository.loadContext(this.requestArg.capture())).willReturn(() -> expectedContext);
93+
given(this.repository.loadDeferredContext(this.requestArg.capture()))
94+
.willReturn(new SupplierDeferredSecurityContext(() -> expectedContext, this.strategy));
9395
FilterChain filterChain = (request, response) -> {
9496
};
9597

web/src/test/java/org/springframework/security/web/context/SecurityContextRepositoryTests.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@
1616

1717
package org.springframework.security.web.context;
1818

19-
import java.util.function.Supplier;
20-
2119
import javax.servlet.http.HttpServletRequest;
2220

2321
import org.junit.jupiter.api.Test;
2422

25-
import org.springframework.security.core.context.SecurityContext;
23+
import org.springframework.security.core.context.DeferredSecurityContext;
2624
import org.springframework.security.core.context.SecurityContextImpl;
2725

2826
import static org.mockito.ArgumentMatchers.any;
@@ -42,8 +40,8 @@ class SecurityContextRepositoryTests {
4240
@Test
4341
void loadContextHttpRequestResponseHolderWhenInvokeSupplierTwiceThenOnlyInvokesLoadContextOnce() {
4442
given(this.repository.loadContext(any(HttpRequestResponseHolder.class))).willReturn(new SecurityContextImpl());
45-
Supplier<SecurityContext> deferredContext = this.repository.loadContext(mock(HttpServletRequest.class));
46-
verify(this.repository).loadContext(any(HttpServletRequest.class));
43+
DeferredSecurityContext deferredContext = this.repository.loadDeferredContext(mock(HttpServletRequest.class));
44+
verify(this.repository).loadDeferredContext(any(HttpServletRequest.class));
4745
deferredContext.get();
4846
verify(this.repository).loadContext(any(HttpRequestResponseHolder.class));
4947
deferredContext.get();

0 commit comments

Comments
 (0)