parent
dbcb5004b4
commit
87ed31a99c
|
@ -37,6 +37,7 @@ import org.springframework.security.web.authentication.ui.DefaultLoginPageGenera
|
|||
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
|
||||
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
|
||||
import org.springframework.security.web.csrf.CsrfFilter;
|
||||
|
@ -70,6 +71,7 @@ final class FilterOrderRegistration {
|
|||
put(ChannelProcessingFilter.class, order.next());
|
||||
order.next(); // gh-8105
|
||||
put(WebAsyncManagerIntegrationFilter.class, order.next());
|
||||
put(SecurityContextHolderFilter.class, order.next());
|
||||
put(SecurityContextPersistenceFilter.class, order.next());
|
||||
put(HeaderWriterFilter.class, order.next());
|
||||
put(CorsFilter.class, order.next());
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.springframework.security.web.authentication.SavedRequestAwareAuthenti
|
|||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||
import org.springframework.security.web.context.SecurityContextRepository;
|
||||
import org.springframework.security.web.savedrequest.RequestCache;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
||||
|
@ -146,6 +147,11 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecur
|
|||
return getSelf();
|
||||
}
|
||||
|
||||
public T securityContextRepository(SecurityContextRepository securityContextRepository) {
|
||||
this.authFilter.setSecurityContextRepository(securityContextRepository);
|
||||
return getSelf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link RequestMatcher} given a loginProcessingUrl
|
||||
* @param loginProcessingUrl creates the {@link RequestMatcher} based upon the
|
||||
|
@ -287,6 +293,12 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecur
|
|||
if (rememberMeServices != null) {
|
||||
this.authFilter.setRememberMeServices(rememberMeServices);
|
||||
}
|
||||
SecurityContextConfigurer securityContextConfigurer = http.getConfigurer(SecurityContextConfigurer.class);
|
||||
if (securityContextConfigurer != null && securityContextConfigurer.isRequireExplicitSave()) {
|
||||
SecurityContextRepository securityContextRepository = securityContextConfigurer
|
||||
.getSecurityContextRepository();
|
||||
this.authFilter.setSecurityContextRepository(securityContextRepository);
|
||||
}
|
||||
F filter = postProcess(this.authFilter);
|
||||
http.addFilter(filter);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.springframework.security.config.http.SessionCreationPolicy;
|
|||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||
import org.springframework.security.web.context.SecurityContextRepository;
|
||||
|
||||
|
@ -62,6 +63,8 @@ import org.springframework.security.web.context.SecurityContextRepository;
|
|||
public final class SecurityContextConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
extends AbstractHttpConfigurer<SecurityContextConfigurer<H>, H> {
|
||||
|
||||
private boolean requireExplicitSave;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @see HttpSecurity#securityContext()
|
||||
|
@ -79,13 +82,34 @@ public final class SecurityContextConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void configure(H http) {
|
||||
SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
|
||||
public SecurityContextConfigurer<H> requireExplicitSave(boolean requireExplicitSave) {
|
||||
this.requireExplicitSave = requireExplicitSave;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isRequireExplicitSave() {
|
||||
return this.requireExplicitSave;
|
||||
}
|
||||
|
||||
SecurityContextRepository getSecurityContextRepository() {
|
||||
SecurityContextRepository securityContextRepository = getBuilder()
|
||||
.getSharedObject(SecurityContextRepository.class);
|
||||
if (securityContextRepository == null) {
|
||||
securityContextRepository = new HttpSessionSecurityContextRepository();
|
||||
}
|
||||
return securityContextRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void configure(H http) {
|
||||
SecurityContextRepository securityContextRepository = getSecurityContextRepository();
|
||||
if (this.requireExplicitSave) {
|
||||
SecurityContextHolderFilter securityContextHolderFilter = postProcess(
|
||||
new SecurityContextHolderFilter(securityContextRepository));
|
||||
http.addFilter(securityContextHolderFilter);
|
||||
}
|
||||
else {
|
||||
SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
|
||||
securityContextRepository);
|
||||
SessionManagementConfigurer<?> sessionManagement = http.getConfigurer(SessionManagementConfigurer.class);
|
||||
|
@ -97,5 +121,6 @@ public final class SecurityContextConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
securityContextFilter = postProcess(securityContextFilter);
|
||||
http.addFilter(securityContextFilter);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -236,8 +236,8 @@ final class AuthenticationConfigBuilder {
|
|||
|
||||
AuthenticationConfigBuilder(Element element, boolean forceAutoConfig, ParserContext pc,
|
||||
SessionCreationPolicy sessionPolicy, BeanReference requestCache, BeanReference authenticationManager,
|
||||
BeanReference sessionStrategy, BeanReference portMapper, BeanReference portResolver,
|
||||
BeanMetadataElement csrfLogoutHandler) {
|
||||
BeanReference authenticationFilterSecurityContextRepositoryRef, BeanReference sessionStrategy,
|
||||
BeanReference portMapper, BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) {
|
||||
this.httpElt = element;
|
||||
this.pc = pc;
|
||||
this.requestCache = requestCache;
|
||||
|
@ -251,10 +251,12 @@ final class AuthenticationConfigBuilder {
|
|||
createRememberMeFilter(authenticationManager);
|
||||
createBasicFilter(authenticationManager);
|
||||
createBearerTokenAuthenticationFilter(authenticationManager);
|
||||
createFormLoginFilter(sessionStrategy, authenticationManager);
|
||||
createOAuth2ClientFilters(sessionStrategy, requestCache, authenticationManager);
|
||||
createOpenIDLoginFilter(sessionStrategy, authenticationManager);
|
||||
createSaml2LoginFilter(authenticationManager);
|
||||
createFormLoginFilter(sessionStrategy, authenticationManager, authenticationFilterSecurityContextRepositoryRef);
|
||||
createOAuth2ClientFilters(sessionStrategy, requestCache, authenticationManager,
|
||||
authenticationFilterSecurityContextRepositoryRef);
|
||||
createOpenIDLoginFilter(sessionStrategy, authenticationManager,
|
||||
authenticationFilterSecurityContextRepositoryRef);
|
||||
createSaml2LoginFilter(authenticationManager, authenticationFilterSecurityContextRepositoryRef);
|
||||
createX509Filter(authenticationManager);
|
||||
createJeeFilter(authenticationManager);
|
||||
createLogoutFilter();
|
||||
|
@ -290,7 +292,8 @@ final class AuthenticationConfigBuilder {
|
|||
this.rememberMeProviderRef = new RuntimeBeanReference(id);
|
||||
}
|
||||
|
||||
void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager) {
|
||||
void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager,
|
||||
BeanReference authenticationFilterSecurityContextRepositoryRef) {
|
||||
Element formLoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.FORM_LOGIN);
|
||||
RootBeanDefinition formFilter = null;
|
||||
if (formLoginElt != null || this.autoConfig) {
|
||||
|
@ -306,6 +309,10 @@ final class AuthenticationConfigBuilder {
|
|||
if (formFilter != null) {
|
||||
formFilter.getPropertyValues().addPropertyValue("allowSessionCreation", this.allowSessionCreation);
|
||||
formFilter.getPropertyValues().addPropertyValue("authenticationManager", authManager);
|
||||
if (authenticationFilterSecurityContextRepositoryRef != null) {
|
||||
formFilter.getPropertyValues().addPropertyValue("securityContextRepository",
|
||||
authenticationFilterSecurityContextRepositoryRef);
|
||||
}
|
||||
// Id is required by login page filter
|
||||
this.formFilterId = this.pc.getReaderContext().generateBeanName(formFilter);
|
||||
this.pc.registerBeanComponent(new BeanComponentDefinition(formFilter, this.formFilterId));
|
||||
|
@ -314,13 +321,15 @@ final class AuthenticationConfigBuilder {
|
|||
}
|
||||
|
||||
void createOAuth2ClientFilters(BeanReference sessionStrategy, BeanReference requestCache,
|
||||
BeanReference authenticationManager) {
|
||||
createOAuth2LoginFilter(sessionStrategy, authenticationManager);
|
||||
createOAuth2ClientFilter(requestCache, authenticationManager);
|
||||
BeanReference authenticationManager, BeanReference authenticationFilterSecurityContextRepositoryRef) {
|
||||
createOAuth2LoginFilter(sessionStrategy, authenticationManager,
|
||||
authenticationFilterSecurityContextRepositoryRef);
|
||||
createOAuth2ClientFilter(requestCache, authenticationManager, authenticationFilterSecurityContextRepositoryRef);
|
||||
registerOAuth2ClientPostProcessors();
|
||||
}
|
||||
|
||||
void createOAuth2LoginFilter(BeanReference sessionStrategy, BeanReference authManager) {
|
||||
void createOAuth2LoginFilter(BeanReference sessionStrategy, BeanReference authManager,
|
||||
BeanReference authenticationFilterSecurityContextRepositoryRef) {
|
||||
Element oauth2LoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_LOGIN);
|
||||
if (oauth2LoginElt == null) {
|
||||
return;
|
||||
|
@ -332,6 +341,10 @@ final class AuthenticationConfigBuilder {
|
|||
BeanDefinition defaultAuthorizedClientRepository = parser.getDefaultAuthorizedClientRepository();
|
||||
registerDefaultAuthorizedClientRepositoryIfNecessary(defaultAuthorizedClientRepository);
|
||||
oauth2LoginFilterBean.getPropertyValues().addPropertyValue("authenticationManager", authManager);
|
||||
if (authenticationFilterSecurityContextRepositoryRef != null) {
|
||||
oauth2LoginFilterBean.getPropertyValues().addPropertyValue("securityContextRepository",
|
||||
authenticationFilterSecurityContextRepositoryRef);
|
||||
}
|
||||
|
||||
// retrieve the other bean result
|
||||
BeanDefinition oauth2LoginAuthProvider = parser.getOAuth2LoginAuthenticationProvider();
|
||||
|
@ -361,14 +374,15 @@ final class AuthenticationConfigBuilder {
|
|||
this.oauth2LoginOidcAuthenticationProviderRef = new RuntimeBeanReference(oauth2LoginOidcAuthProviderId);
|
||||
}
|
||||
|
||||
void createOAuth2ClientFilter(BeanReference requestCache, BeanReference authenticationManager) {
|
||||
void createOAuth2ClientFilter(BeanReference requestCache, BeanReference authenticationManager,
|
||||
BeanReference authenticationFilterSecurityContextRepositoryRef) {
|
||||
Element oauth2ClientElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_CLIENT);
|
||||
if (oauth2ClientElt == null) {
|
||||
return;
|
||||
}
|
||||
this.oauth2ClientEnabled = true;
|
||||
OAuth2ClientBeanDefinitionParser parser = new OAuth2ClientBeanDefinitionParser(requestCache,
|
||||
authenticationManager);
|
||||
authenticationManager, authenticationFilterSecurityContextRepositoryRef);
|
||||
parser.parse(oauth2ClientElt, this.pc);
|
||||
BeanDefinition defaultAuthorizedClientRepository = parser.getDefaultAuthorizedClientRepository();
|
||||
registerDefaultAuthorizedClientRepositoryIfNecessary(defaultAuthorizedClientRepository);
|
||||
|
@ -413,7 +427,8 @@ final class AuthenticationConfigBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
void createOpenIDLoginFilter(BeanReference sessionStrategy, BeanReference authManager) {
|
||||
void createOpenIDLoginFilter(BeanReference sessionStrategy, BeanReference authManager,
|
||||
BeanReference authenticationFilterSecurityContextRepositoryRef) {
|
||||
Element openIDLoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OPENID_LOGIN);
|
||||
RootBeanDefinition openIDFilter = null;
|
||||
if (openIDLoginElt != null) {
|
||||
|
@ -422,6 +437,10 @@ final class AuthenticationConfigBuilder {
|
|||
if (openIDFilter != null) {
|
||||
openIDFilter.getPropertyValues().addPropertyValue("allowSessionCreation", this.allowSessionCreation);
|
||||
openIDFilter.getPropertyValues().addPropertyValue("authenticationManager", authManager);
|
||||
if (authenticationFilterSecurityContextRepositoryRef != null) {
|
||||
openIDFilter.getPropertyValues().addPropertyValue("securityContextRepository",
|
||||
authenticationFilterSecurityContextRepositoryRef);
|
||||
}
|
||||
// Required by login page filter
|
||||
this.openIDFilterId = this.pc.getReaderContext().generateBeanName(openIDFilter);
|
||||
this.pc.registerBeanComponent(new BeanComponentDefinition(openIDFilter, this.openIDFilterId));
|
||||
|
@ -430,14 +449,16 @@ final class AuthenticationConfigBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
private void createSaml2LoginFilter(BeanReference authenticationManager) {
|
||||
private void createSaml2LoginFilter(BeanReference authenticationManager,
|
||||
BeanReference authenticationFilterSecurityContextRepositoryRef) {
|
||||
Element saml2LoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.SAML2_LOGIN);
|
||||
if (saml2LoginElt == null) {
|
||||
return;
|
||||
}
|
||||
Saml2LoginBeanDefinitionParser parser = new Saml2LoginBeanDefinitionParser(this.csrfIgnoreRequestMatchers,
|
||||
this.portMapper, this.portResolver, this.requestCache, this.allowSessionCreation, authenticationManager,
|
||||
this.authenticationProviders, this.defaultEntryPointMappings);
|
||||
authenticationFilterSecurityContextRepositoryRef, this.authenticationProviders,
|
||||
this.defaultEntryPointMappings);
|
||||
BeanDefinition saml2WebSsoAuthenticationFilter = parser.parse(saml2LoginElt, this.pc);
|
||||
this.saml2AuthorizationRequestFilter = parser.getSaml2WebSsoAuthenticationRequestFilter();
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ import org.springframework.security.web.authentication.session.RegisterSessionAu
|
|||
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.security.web.context.NullSecurityContextRepository;
|
||||
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
|
||||
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
|
||||
|
@ -104,6 +105,8 @@ class HttpConfigurationBuilder {
|
|||
|
||||
private static final String ATT_SECURITY_CONTEXT_REPOSITORY = "security-context-repository-ref";
|
||||
|
||||
private static final String ATT_SECURITY_CONTEXT_EXPLICIT_SAVE = "security-context-explicit-save";
|
||||
|
||||
private static final String ATT_INVALID_SESSION_STRATEGY_REF = "invalid-session-strategy-ref";
|
||||
|
||||
private static final String ATT_DISABLE_URL_REWRITING = "disable-url-rewriting";
|
||||
|
@ -202,8 +205,7 @@ class HttpConfigurationBuilder {
|
|||
this.sessionPolicy = !StringUtils.hasText(createSession) ? SessionCreationPolicy.IF_REQUIRED
|
||||
: createPolicy(createSession);
|
||||
createCsrfFilter();
|
||||
createSecurityContextRepository();
|
||||
createSecurityContextPersistenceFilter();
|
||||
createSecurityPersistence();
|
||||
createSessionManagementFilters();
|
||||
createWebAsyncManagerFilter();
|
||||
createRequestCacheFilter();
|
||||
|
@ -279,9 +281,27 @@ class HttpConfigurationBuilder {
|
|||
return lowerCase ? path.toLowerCase() : path;
|
||||
}
|
||||
|
||||
BeanReference getSecurityContextRepositoryForAuthenticationFilters() {
|
||||
return (isExplicitSave()) ? this.contextRepoRef : null;
|
||||
}
|
||||
|
||||
private void createSecurityPersistence() {
|
||||
createSecurityContextRepository();
|
||||
if (isExplicitSave()) {
|
||||
createSecurityContextHolderFilter();
|
||||
}
|
||||
else {
|
||||
createSecurityContextPersistenceFilter();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExplicitSave() {
|
||||
String explicitSaveAttr = this.httpElt.getAttribute(ATT_SECURITY_CONTEXT_EXPLICIT_SAVE);
|
||||
return Boolean.parseBoolean(explicitSaveAttr);
|
||||
}
|
||||
|
||||
private void createSecurityContextPersistenceFilter() {
|
||||
BeanDefinitionBuilder scpf = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextPersistenceFilter.class);
|
||||
String disableUrlRewriting = this.httpElt.getAttribute(ATT_DISABLE_URL_REWRITING);
|
||||
switch (this.sessionPolicy) {
|
||||
case ALWAYS:
|
||||
scpf.addPropertyValue("forceEagerSessionCreation", Boolean.TRUE);
|
||||
|
@ -332,6 +352,12 @@ class HttpConfigurationBuilder {
|
|||
this.contextRepoRef = new RuntimeBeanReference(repoRef);
|
||||
}
|
||||
|
||||
private void createSecurityContextHolderFilter() {
|
||||
BeanDefinitionBuilder filter = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextHolderFilter.class);
|
||||
filter.addConstructorArgValue(this.contextRepoRef);
|
||||
this.securityContextPersistenceFilter = filter.getBeanDefinition();
|
||||
}
|
||||
|
||||
private void createSessionManagementFilters() {
|
||||
Element sessionMgmtElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.SESSION_MANAGEMENT);
|
||||
Element sessionCtrlElt = null;
|
||||
|
|
|
@ -144,9 +144,11 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||
boolean forceAutoConfig = isDefaultHttpConfig(element);
|
||||
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, forceAutoConfig, pc, portMapper,
|
||||
portResolver, authenticationManager);
|
||||
httpBldr.getSecurityContextRepositoryForAuthenticationFilters();
|
||||
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, forceAutoConfig, pc,
|
||||
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
|
||||
httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler());
|
||||
httpBldr.getSecurityContextRepositoryForAuthenticationFilters(), httpBldr.getSessionStrategy(),
|
||||
portMapper, portResolver, httpBldr.getCsrfLogoutHandler());
|
||||
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
|
||||
httpBldr.setEntryPoint(authBldr.getEntryPointBean());
|
||||
httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());
|
||||
|
|
|
@ -50,6 +50,8 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
private final BeanReference authenticationManager;
|
||||
|
||||
private final BeanReference authenticationFilterSecurityContextRepositoryRef;
|
||||
|
||||
private BeanDefinition defaultAuthorizedClientRepository;
|
||||
|
||||
private BeanDefinition authorizationRequestRedirectFilter;
|
||||
|
@ -58,9 +60,11 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
private BeanDefinition authorizationCodeAuthenticationProvider;
|
||||
|
||||
OAuth2ClientBeanDefinitionParser(BeanReference requestCache, BeanReference authenticationManager) {
|
||||
OAuth2ClientBeanDefinitionParser(BeanReference requestCache, BeanReference authenticationManager,
|
||||
BeanReference authenticationFilterSecurityContextRepositoryRef) {
|
||||
this.requestCache = requestCache;
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.authenticationFilterSecurityContextRepositoryRef = authenticationFilterSecurityContextRepositoryRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -92,11 +96,16 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
|
|||
this.authorizationRequestRedirectFilter = authorizationRequestRedirectFilterBuilder
|
||||
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository)
|
||||
.addPropertyValue("requestCache", this.requestCache).getBeanDefinition();
|
||||
this.authorizationCodeGrantFilter = BeanDefinitionBuilder
|
||||
BeanDefinitionBuilder authorizationCodeGrantFilterBldr = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(OAuth2AuthorizationCodeGrantFilter.class)
|
||||
.addConstructorArgValue(clientRegistrationRepository).addConstructorArgValue(authorizedClientRepository)
|
||||
.addConstructorArgValue(this.authenticationManager)
|
||||
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository).getBeanDefinition();
|
||||
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository);
|
||||
if (this.authenticationFilterSecurityContextRepositoryRef != null) {
|
||||
authorizationCodeGrantFilterBldr.addPropertyValue("securityContextRepository",
|
||||
this.authenticationFilterSecurityContextRepositoryRef);
|
||||
}
|
||||
this.authorizationCodeGrantFilter = authorizationCodeGrantFilterBldr.getBeanDefinition();
|
||||
|
||||
BeanMetadataElement accessTokenResponseClient = getAccessTokenResponseClient(authorizationCodeGrantElt);
|
||||
this.authorizationCodeAuthenticationProvider = BeanDefinitionBuilder
|
||||
|
|
|
@ -85,6 +85,8 @@ final class Saml2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
private final BeanReference authenticationManager;
|
||||
|
||||
private final BeanReference authenticationFilterSecurityContextRepositoryRef;
|
||||
|
||||
private final List<BeanReference> authenticationProviders;
|
||||
|
||||
private final Map<BeanDefinition, BeanMetadataElement> entryPoints;
|
||||
|
@ -97,14 +99,15 @@ final class Saml2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
Saml2LoginBeanDefinitionParser(List<BeanDefinition> csrfIgnoreRequestMatchers, BeanReference portMapper,
|
||||
BeanReference portResolver, BeanReference requestCache, boolean allowSessionCreation,
|
||||
BeanReference authenticationManager, List<BeanReference> authenticationProviders,
|
||||
Map<BeanDefinition, BeanMetadataElement> entryPoints) {
|
||||
BeanReference authenticationManager, BeanReference authenticationFilterSecurityContextRepositoryRef,
|
||||
List<BeanReference> authenticationProviders, Map<BeanDefinition, BeanMetadataElement> entryPoints) {
|
||||
this.csrfIgnoreRequestMatchers = csrfIgnoreRequestMatchers;
|
||||
this.portMapper = portMapper;
|
||||
this.portResolver = portResolver;
|
||||
this.requestCache = requestCache;
|
||||
this.allowSessionCreation = allowSessionCreation;
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.authenticationFilterSecurityContextRepositoryRef = authenticationFilterSecurityContextRepositoryRef;
|
||||
this.authenticationProviders = authenticationProviders;
|
||||
this.entryPoints = entryPoints;
|
||||
}
|
||||
|
@ -148,6 +151,7 @@ final class Saml2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
resolveAuthenticationSuccessHandler(element, saml2WebSsoAuthenticationFilterBuilder);
|
||||
resolveAuthenticationFailureHandler(element, saml2WebSsoAuthenticationFilterBuilder);
|
||||
resolveAuthenticationManager(element, saml2WebSsoAuthenticationFilterBuilder);
|
||||
resolveSecurityContextRepository(element, saml2WebSsoAuthenticationFilterBuilder);
|
||||
// Configure the Saml2WebSsoAuthenticationRequestFilter
|
||||
this.saml2WebSsoAuthenticationRequestFilter = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(Saml2WebSsoAuthenticationRequestFilter.class)
|
||||
|
@ -176,6 +180,14 @@ final class Saml2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
}
|
||||
}
|
||||
|
||||
private void resolveSecurityContextRepository(Element element,
|
||||
BeanDefinitionBuilder saml2WebSsoAuthenticationFilterBuilder) {
|
||||
if (this.authenticationFilterSecurityContextRepositoryRef != null) {
|
||||
saml2WebSsoAuthenticationFilterBuilder.addPropertyValue("securityContextRepository",
|
||||
this.authenticationFilterSecurityContextRepositoryRef);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveLoginPage(Element element, ParserContext parserContext) {
|
||||
String loginPage = element.getAttribute(ATT_LOGIN_PAGE);
|
||||
Object source = parserContext.extractSource(element);
|
||||
|
|
|
@ -333,6 +333,9 @@ http.attlist &=
|
|||
http.attlist &=
|
||||
## A reference to a SecurityContextRepository bean. This can be used to customize how the SecurityContext is stored between requests.
|
||||
attribute security-context-repository-ref {xsd:token}?
|
||||
http.attlist &=
|
||||
## Optional attribute that specifies that the SecurityContext should require explicit saving rather than being synchronized from the SecurityContextHolder. Defaults to "false".
|
||||
attribute security-context-explicit-save {xsd:boolean}?
|
||||
http.attlist &=
|
||||
request-matcher?
|
||||
http.attlist &=
|
||||
|
|
|
@ -1237,6 +1237,13 @@
|
|||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="security-context-explicit-save" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Optional attribute that specifies that the SecurityContext should require explicit saving
|
||||
rather than being synchronized from the SecurityContextHolder. Defaults to "false".
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="request-matcher">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -33,8 +37,11 @@ import org.springframework.security.config.test.SpringTestContext;
|
|||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.context.HttpRequestResponseHolder;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.security.web.context.NullSecurityContextRepository;
|
||||
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||
import org.springframework.security.web.context.SecurityContextRepository;
|
||||
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
|
||||
|
@ -110,6 +117,27 @@ public class SecurityContextConfigurerTests {
|
|||
assertThat(session).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requireExplicitSave() throws Exception {
|
||||
HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository();
|
||||
SpringTestContext testContext = this.spring.register(RequireExplicitSaveConfig.class);
|
||||
testContext.autowire();
|
||||
FilterChainProxy filterChainProxy = testContext.getContext().getBean(FilterChainProxy.class);
|
||||
// @formatter:off
|
||||
List<Class<? extends Filter>> filterTypes = filterChainProxy.getFilters("/")
|
||||
.stream()
|
||||
.map(Filter::getClass)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(filterTypes)
|
||||
.contains(SecurityContextHolderFilter.class)
|
||||
.doesNotContain(SecurityContextPersistenceFilter.class);
|
||||
// @formatter:on
|
||||
MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn();
|
||||
SecurityContext securityContext = repository
|
||||
.loadContext(new HttpRequestResponseHolder(mvcResult.getRequest(), mvcResult.getResponse()));
|
||||
assertThat(securityContext.getAuthentication()).isNotNull();
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
|
@ -264,4 +292,29 @@ public class SecurityContextConfigurerTests {
|
|||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class RequireExplicitSaveConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.formLogin(withDefaults())
|
||||
.securityContext((securityContext) -> securityContext
|
||||
.requireExplicitSave(true)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
// @formatter:off
|
||||
auth
|
||||
.inMemoryAuthentication()
|
||||
.withUser(PasswordEncodedUser.user());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -122,9 +122,11 @@ import static org.mockito.BDDMockito.willAnswer;
|
|||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
|
@ -462,6 +464,37 @@ public class MiscHttpConfigTests {
|
|||
any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenExplicitSaveAndRepositoryAndAuthenticatingThenConsultsCustomSecurityContextRepository()
|
||||
throws Exception {
|
||||
this.spring.configLocations(xml("ExplicitSaveAndExplicitRepository")).autowire();
|
||||
SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class);
|
||||
SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "password"));
|
||||
given(repository.loadContext(any(HttpRequestResponseHolder.class))).willReturn(context);
|
||||
// @formatter:off
|
||||
MvcResult result = this.mvc.perform(formLogin())
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(authenticated())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
verify(repository, atLeastOnce()).saveContext(any(SecurityContext.class), any(HttpServletRequest.class),
|
||||
any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenExplicitSaveAndExplicitSaveAndAuthenticatingThenConsultsCustomSecurityContextRepository()
|
||||
throws Exception {
|
||||
this.spring.configLocations(xml("ExplicitSave")).autowire();
|
||||
SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class);
|
||||
// @formatter:off
|
||||
MvcResult result = this.mvc.perform(formLogin())
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
assertThat(repository.loadContext(new HttpRequestResponseHolder(result.getRequest(), result.getResponse()))
|
||||
.getAuthentication()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenUsingInterceptUrlExpressionsThenAuthorizesAccordingly() throws Exception {
|
||||
this.spring.configLocations(xml("InterceptUrlExpressions")).autowire();
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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
|
||||
~
|
||||
~ https://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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http security-context-explicit-save="true">
|
||||
<form-login/>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
|
||||
<b:import resource="MiscHttpConfigTests-controllers.xml"/>
|
||||
<b:import resource="userservice.xml"/>
|
||||
</b:beans>
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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
|
||||
~
|
||||
~ https://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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/security
|
||||
https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http create-session="always" security-context-repository-ref="repo" security-context-explicit-save="true">
|
||||
<form-login/>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
|
||||
<b:bean name="repo" class="org.mockito.Mockito" factory-method="mock">
|
||||
<b:constructor-arg value="org.springframework.security.web.context.SecurityContextRepository"/>
|
||||
</b:bean>
|
||||
|
||||
<b:import resource="MiscHttpConfigTests-controllers.xml"/>
|
||||
<b:import resource="userservice.xml"/>
|
||||
</b:beans>
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 102 KiB |
|
@ -125,6 +125,12 @@ A request pattern can be mapped to an empty filter chain, by setting this attrib
|
|||
No security will be applied and none of Spring Security's features will be available.
|
||||
|
||||
|
||||
[[nsa-http-security-context-explicit-save]]
|
||||
* **security-context-explicit-save**
|
||||
If true, use `SecurityContextHolderFilter` instead of `SecurityContextPersistenceFilter`.
|
||||
Requires explicit save
|
||||
|
||||
|
||||
[[nsa-http-security-context-repository-ref]]
|
||||
* **security-context-repository-ref**
|
||||
Allows injection of a custom `SecurityContextRepository` into the `SecurityContextPersistenceFilter`.
|
||||
|
|
|
@ -88,6 +88,34 @@ Depending on the servlet container implementation, the error means that any `Sec
|
|||
When the error dispatch is made, there is no `SecurityContext` established.
|
||||
This means that the error page cannot use the `SecurityContext` for authorization or displaying the current user unless the `SecurityContext` is persisted somehow.
|
||||
|
||||
.Use RequestAttributeSecurityContextRepository
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.securityContext((securityContext) -> securityContext
|
||||
.securityContextRepository(new RequestAttributeSecurityContextRepository())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http security-context-repository-ref="contextRepository">
|
||||
<!-- ... -->
|
||||
</http>
|
||||
<b:bean name="contextRepository"
|
||||
class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
[[securitycontextpersistencefilter]]
|
||||
== SecurityContextPersistenceFilter
|
||||
|
||||
The {security-api-url}org/springframework/security/web/context/SecurityContextPersistenceFilter.html[`SecurityContextPersistenceFilter`] is responsible for persisting the `SecurityContext` between requests using the xref::servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`].
|
||||
|
@ -105,3 +133,40 @@ This means that establishing an `HttpSession` would not be possible in step 3 be
|
|||
Another situation that can happen is that if a client authenticates successfully, the response is committed before `SecurityContextPersistenceFilter` completes, and the client makes a second request before the `SecurityContextPersistenceFilter` completes the wrong authentication could be present in the second request.
|
||||
|
||||
To avoid these problems, the `SecurityContextPersistenceFilter` wraps both the `HttpServletRequest` and the `HttpServletResponse` to detect if the `SecurityContext` has changed and if so save the `SecurityContext` just before the response is committed.
|
||||
|
||||
[[securitycontextholderfilter]]
|
||||
== SecurityContextHolderFilter
|
||||
|
||||
The {security-api-url}org/springframework/security/web/context/SecurityContextHolderFilter.html[`SecurityContextHolderFilter`] is responsible for loading the `SecurityContext` between requests using the xref::servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`].
|
||||
|
||||
image::{figures}/securitycontextholderfilter.png[]
|
||||
|
||||
<1> Before running the rest of the application, `SecurityContextHolderFilter` loads the `SecurityContext` from the `SecurityContextRepository` and sets it on the `SecurityContextHolder`.
|
||||
<2> Next, the application is ran.
|
||||
|
||||
Unlike, xref:servlet/authentication/persistence.adoc#securitycontextpersistencefilter[`SecurityContextPersisteneFilter`], `SecurityContextHolderFilter` only loads the `SecurityContext` it does not save the `SecurityContext`.
|
||||
This means that when using `SecurityContextHolderFilter`, it is required that the `SecurityContext` is explicitly saved.
|
||||
|
||||
.Explicit Saving of SecurityContext
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.securityContext((securityContext) -> securityContext
|
||||
.requireExplicitSave(true)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http security-context-explicit-save="true">
|
||||
<!-- ... -->
|
||||
</http>
|
||||
----
|
||||
====
|
|
@ -3,3 +3,6 @@
|
|||
|
||||
Spring Security 5.7 provides a number of new features.
|
||||
Below are the highlights of the release.
|
||||
|
||||
* xref:servlet/authentication/persistence.adoc#requestattributesecuritycontextrepository[`RequestAttributeSecurityContextRepository`]
|
||||
* xref:servlet/authentication/persistence.adoc#securitycontextholderfilter[`SecurityContextHolderFilter`] - Ability to require explicit saving of the `SecurityContext`.
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
|||
import org.springframework.security.config.BeanIds;
|
||||
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||
import org.springframework.security.web.context.SecurityContextRepository;
|
||||
import org.springframework.security.web.csrf.CsrfFilter;
|
||||
|
@ -61,11 +62,15 @@ public abstract class WebTestUtils {
|
|||
*/
|
||||
public static SecurityContextRepository getSecurityContextRepository(HttpServletRequest request) {
|
||||
SecurityContextPersistenceFilter filter = findFilter(request, SecurityContextPersistenceFilter.class);
|
||||
if (filter == null) {
|
||||
return DEFAULT_CONTEXT_REPO;
|
||||
}
|
||||
if (filter != null) {
|
||||
return (SecurityContextRepository) ReflectionTestUtils.getField(filter, "repo");
|
||||
}
|
||||
SecurityContextHolderFilter holderFilter = findFilter(request, SecurityContextHolderFilter.class);
|
||||
if (holderFilter != null) {
|
||||
return (SecurityContextRepository) ReflectionTestUtils.getField(holderFilter, "securityContextRepository");
|
||||
}
|
||||
return DEFAULT_CONTEXT_REPO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link SecurityContextRepository} for the specified
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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 java.io.IOException;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
* A {@link javax.servlet.Filter} that uses the {@link SecurityContextRepository} to
|
||||
* obtain the {@link SecurityContext} and set it on the {@link SecurityContextHolder}.
|
||||
* This is similar to {@link SecurityContextPersistenceFilter} except that the
|
||||
* {@link SecurityContextRepository#saveContext(SecurityContext, HttpServletRequest, HttpServletResponse)}
|
||||
* must be explicitly invoked to save the {@link SecurityContext}. This improves the
|
||||
* efficiency and provides better flexibility by allowing different authentication
|
||||
* mechanisms to choose individually if authentication should be persisted.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.7
|
||||
*/
|
||||
public class SecurityContextHolderFilter extends OncePerRequestFilter {
|
||||
|
||||
private final SecurityContextRepository securityContextRepository;
|
||||
|
||||
private boolean shouldNotFilterErrorDispatch;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param securityContextRepository the repository to use. Cannot be null.
|
||||
*/
|
||||
public SecurityContextHolderFilter(SecurityContextRepository securityContextRepository) {
|
||||
Assert.notNull(securityContextRepository, "securityContextRepository cannot be null");
|
||||
this.securityContextRepository = securityContextRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
SecurityContext securityContext = this.securityContextRepository
|
||||
.loadContext(new HttpRequestResponseHolder(request, response));
|
||||
try {
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
finally {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilterErrorDispatch() {
|
||||
return this.shouldNotFilterErrorDispatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables {@link SecurityContextHolderFilter} for error dispatch.
|
||||
* @param shouldNotFilterErrorDispatch if the Filter should be disabled for error
|
||||
* dispatch. Default is false.
|
||||
*/
|
||||
public void setShouldNotFilterErrorDispatch(boolean shouldNotFilterErrorDispatch) {
|
||||
this.shouldNotFilterErrorDispatch = shouldNotFilterErrorDispatch;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SecurityContextHolderFilterTests {
|
||||
|
||||
@Mock
|
||||
private SecurityContextRepository repository;
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
|
||||
@Mock
|
||||
private HttpServletResponse response;
|
||||
|
||||
@Mock
|
||||
private FilterChain chain;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<HttpRequestResponseHolder> requestResponse;
|
||||
|
||||
private SecurityContextHolderFilter filter;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.filter = new SecurityContextHolderFilter(this.repository);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void doFilterThenSetsAndClearsSecurityContextHolder() throws Exception {
|
||||
Authentication authentication = TestAuthentication.authenticatedUser();
|
||||
SecurityContext expectedContext = new SecurityContextImpl(authentication);
|
||||
given(this.repository.loadContext(this.requestResponse.capture())).willReturn(expectedContext);
|
||||
FilterChain filterChain = (request, response) -> assertThat(SecurityContextHolder.getContext())
|
||||
.isEqualTo(expectedContext);
|
||||
|
||||
this.filter.doFilter(this.request, this.response, filterChain);
|
||||
|
||||
assertThat(SecurityContextHolder.getContext()).isEqualTo(SecurityContextHolder.createEmptyContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFilterErrorDispatchWhenDefault() {
|
||||
assertThat(this.filter.shouldNotFilterErrorDispatch()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFilterErrorDispatchWhenOverridden() {
|
||||
this.filter.setShouldNotFilterErrorDispatch(true);
|
||||
assertThat(this.filter.shouldNotFilterErrorDispatch()).isTrue();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue