Add DisableUrlRewritingFilter

Closes gh-11084
This commit is contained in:
Rob Winch 2022-04-06 14:42:47 -05:00
parent 32b83aae63
commit 39b0620a84
14 changed files with 375 additions and 36 deletions

View File

@ -42,6 +42,7 @@ import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter; import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.DisableEncodeUrlFilter;
import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.session.SessionManagementFilter;
/** /**
@ -124,6 +125,7 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
* The ordering of the Filters is: * The ordering of the Filters is:
* *
* <ul> * <ul>
* <li>{@link DisableEncodeUrlFilter}</li>
* <li>{@link ChannelProcessingFilter}</li> * <li>{@link ChannelProcessingFilter}</li>
* <li>{@link SecurityContextPersistenceFilter}</li> * <li>{@link SecurityContextPersistenceFilter}</li>
* <li>{@link LogoutFilter}</li> * <li>{@link LogoutFilter}</li>

View File

@ -46,6 +46,7 @@ import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter; import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.DisableEncodeUrlFilter;
import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
@ -68,6 +69,7 @@ final class FilterOrderRegistration {
FilterOrderRegistration() { FilterOrderRegistration() {
Step order = new Step(INITIAL_ORDER, ORDER_STEP); Step order = new Step(INITIAL_ORDER, ORDER_STEP);
put(DisableEncodeUrlFilter.class, order.next());
put(ChannelProcessingFilter.class, order.next()); put(ChannelProcessingFilter.class, order.next());
order.next(); // gh-8105 order.next(); // gh-8105
put(WebAsyncManagerIntegrationFilter.class, order.next()); put(WebAsyncManagerIntegrationFilter.class, order.next());

View File

@ -52,6 +52,7 @@ import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.session.ConcurrentSessionFilter; import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.DisableEncodeUrlFilter;
import org.springframework.security.web.session.InvalidSessionStrategy; import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy; import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.session.SessionManagementFilter;
@ -376,6 +377,9 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
concurrentSessionFilter = postProcess(concurrentSessionFilter); concurrentSessionFilter = postProcess(concurrentSessionFilter);
http.addFilter(concurrentSessionFilter); http.addFilter(concurrentSessionFilter);
} }
if (!this.enableSessionUrlRewriting) {
http.addFilter(new DisableEncodeUrlFilter());
}
} }
private ConcurrentSessionFilter createConcurrencyFilter(H http) { private ConcurrentSessionFilter createConcurrencyFilter(H http) {

View File

@ -68,6 +68,7 @@ import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter; import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.DisableEncodeUrlFilter;
import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy; import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy; import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;
@ -179,6 +180,8 @@ class HttpConfigurationBuilder {
private BeanDefinition csrfFilter; private BeanDefinition csrfFilter;
private BeanDefinition disableUrlRewriteFilter;
private BeanDefinition wellKnownChangePasswordRedirectFilter; private BeanDefinition wellKnownChangePasswordRedirectFilter;
private BeanMetadataElement csrfLogoutHandler; private BeanMetadataElement csrfLogoutHandler;
@ -204,6 +207,7 @@ class HttpConfigurationBuilder {
String createSession = element.getAttribute(ATT_CREATE_SESSION); String createSession = element.getAttribute(ATT_CREATE_SESSION);
this.sessionPolicy = !StringUtils.hasText(createSession) ? SessionCreationPolicy.IF_REQUIRED this.sessionPolicy = !StringUtils.hasText(createSession) ? SessionCreationPolicy.IF_REQUIRED
: createPolicy(createSession); : createPolicy(createSession);
createDisableEncodeUrlFilter();
createCsrfFilter(); createCsrfFilter();
createSecurityPersistence(); createSecurityPersistence();
createSessionManagementFilters(); createSessionManagementFilters();
@ -319,10 +323,6 @@ class HttpConfigurationBuilder {
private void createSecurityContextRepository() { private void createSecurityContextRepository() {
String repoRef = this.httpElt.getAttribute(ATT_SECURITY_CONTEXT_REPOSITORY); String repoRef = this.httpElt.getAttribute(ATT_SECURITY_CONTEXT_REPOSITORY);
String disableUrlRewriting = this.httpElt.getAttribute(ATT_DISABLE_URL_REWRITING);
if (!StringUtils.hasText(disableUrlRewriting)) {
disableUrlRewriting = "true";
}
if (!StringUtils.hasText(repoRef)) { if (!StringUtils.hasText(repoRef)) {
BeanDefinitionBuilder contextRepo; BeanDefinitionBuilder contextRepo;
if (this.sessionPolicy == SessionCreationPolicy.STATELESS) { if (this.sessionPolicy == SessionCreationPolicy.STATELESS) {
@ -340,7 +340,7 @@ class HttpConfigurationBuilder {
default: default:
contextRepo.addPropertyValue("allowSessionCreation", Boolean.TRUE); contextRepo.addPropertyValue("allowSessionCreation", Boolean.TRUE);
} }
if ("true".equals(disableUrlRewriting)) { if (isDisableUrlRewriting()) {
contextRepo.addPropertyValue("disableUrlRewriting", Boolean.TRUE); contextRepo.addPropertyValue("disableUrlRewriting", Boolean.TRUE);
} }
} }
@ -352,6 +352,11 @@ class HttpConfigurationBuilder {
this.contextRepoRef = new RuntimeBeanReference(repoRef); this.contextRepoRef = new RuntimeBeanReference(repoRef);
} }
private boolean isDisableUrlRewriting() {
String disableUrlRewriting = this.httpElt.getAttribute(ATT_DISABLE_URL_REWRITING);
return !"false".equals(disableUrlRewriting);
}
private void createSecurityContextHolderFilter() { private void createSecurityContextHolderFilter() {
BeanDefinitionBuilder filter = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextHolderFilter.class); BeanDefinitionBuilder filter = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextHolderFilter.class);
filter.addConstructorArgValue(this.contextRepoRef); filter.addConstructorArgValue(this.contextRepoRef);
@ -718,6 +723,12 @@ class HttpConfigurationBuilder {
} }
private void createDisableEncodeUrlFilter() {
if (isDisableUrlRewriting()) {
this.disableUrlRewriteFilter = new RootBeanDefinition(DisableEncodeUrlFilter.class);
}
}
private void createCsrfFilter() { private void createCsrfFilter() {
Element elmt = DomUtils.getChildElementByTagName(this.httpElt, Elements.CSRF); Element elmt = DomUtils.getChildElementByTagName(this.httpElt, Elements.CSRF);
this.csrfParser = new CsrfBeanDefinitionParser(); this.csrfParser = new CsrfBeanDefinitionParser();
@ -757,6 +768,9 @@ class HttpConfigurationBuilder {
List<OrderDecorator> getFilters() { List<OrderDecorator> getFilters() {
List<OrderDecorator> filters = new ArrayList<>(); List<OrderDecorator> filters = new ArrayList<>();
if (this.disableUrlRewriteFilter != null) {
filters.add(new OrderDecorator(this.disableUrlRewriteFilter, SecurityFilters.DISABLE_ENCODE_URL_FILTER));
}
if (this.cpf != null) { if (this.cpf != null) {
filters.add(new OrderDecorator(this.cpf, SecurityFilters.CHANNEL_FILTER)); filters.add(new OrderDecorator(this.cpf, SecurityFilters.CHANNEL_FILTER));
} }

View File

@ -29,6 +29,8 @@ enum SecurityFilters {
FIRST(Integer.MIN_VALUE), FIRST(Integer.MIN_VALUE),
DISABLE_ENCODE_URL_FILTER,
CHANNEL_FILTER, CHANNEL_FILTER,
SECURITY_CONTEXT_FILTER, SECURITY_CONTEXT_FILTER,

View File

@ -1318,4 +1318,4 @@ position =
## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter. ## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
attribute position {named-security-filter} attribute position {named-security-filter}
named-security-filter = "FIRST" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "SAML2_LOGOUT_REQUEST_FILTER" | "SAML2_LOGOUT_RESPONSE_FILTER" | "CSRF_FILTER" | "SAML2_LOGOUT_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "SAML2_AUTHENTICATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "SAML2_AUTHENTICATION_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST" named-security-filter = "FIRST" | "DISABLE_ENCODE_URL_FILTER" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "SAML2_LOGOUT_REQUEST_FILTER" | "SAML2_LOGOUT_RESPONSE_FILTER" | "CSRF_FILTER" | "SAML2_LOGOUT_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "SAML2_AUTHENTICATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "SAML2_AUTHENTICATION_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"

View File

@ -124,7 +124,7 @@
</xs:annotation> </xs:annotation>
<xs:complexType/> <xs:complexType/>
</xs:element> </xs:element>
<xs:attributeGroup name="password-encoder.attlist"> <xs:attributeGroup name="password-encoder.attlist">
<xs:attribute name="ref" type="xs:token"> <xs:attribute name="ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -408,7 +408,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="ldap-ap.attlist"> <xs:attributeGroup name="ldap-ap.attlist">
<xs:attribute name="server-ref" type="xs:token"> <xs:attribute name="server-ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -488,7 +488,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="password-compare.attlist"> <xs:attributeGroup name="password-compare.attlist">
<xs:attribute name="password-attribute" type="xs:token"> <xs:attribute name="password-attribute" type="xs:token">
<xs:annotation> <xs:annotation>
@ -541,7 +541,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="protect.attlist"> <xs:attributeGroup name="protect.attlist">
<xs:attribute name="method" use="required" type="xs:token"> <xs:attribute name="method" use="required" type="xs:token">
<xs:annotation> <xs:annotation>
@ -842,13 +842,13 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="protect-pointcut.attlist"> <xs:attributeGroup name="protect-pointcut.attlist">
<xs:attribute name="expression" use="required" type="xs:string"> <xs:attribute name="expression" use="required" type="xs:string">
<xs:annotation> <xs:annotation>
@ -1323,7 +1323,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="access-denied-handler.attlist"> <xs:attributeGroup name="access-denied-handler.attlist">
<xs:attribute name="ref" type="xs:token"> <xs:attribute name="ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -1348,7 +1348,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="intercept-url.attlist"> <xs:attributeGroup name="intercept-url.attlist">
<xs:attribute name="pattern" type="xs:token"> <xs:attribute name="pattern" type="xs:token">
<xs:annotation> <xs:annotation>
@ -1405,7 +1405,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="logout.attlist"> <xs:attributeGroup name="logout.attlist">
<xs:attribute name="logout-url" type="xs:token"> <xs:attribute name="logout-url" type="xs:token">
<xs:annotation> <xs:annotation>
@ -1452,7 +1452,7 @@
<xs:attributeGroup ref="security:ref"/> <xs:attributeGroup ref="security:ref"/>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:attributeGroup name="form-login.attlist"> <xs:attributeGroup name="form-login.attlist">
<xs:attribute name="login-processing-url" type="xs:token"> <xs:attribute name="login-processing-url" type="xs:token">
<xs:annotation> <xs:annotation>
@ -1967,7 +1967,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:element name="attribute-exchange"> <xs:element name="attribute-exchange">
<xs:annotation> <xs:annotation>
<xs:documentation>Sets up an attribute exchange configuration to request specified attributes from the <xs:documentation>Sets up an attribute exchange configuration to request specified attributes from the
@ -2034,7 +2034,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="saml2-login.attlist"> <xs:attributeGroup name="saml2-login.attlist">
<xs:attribute name="relying-party-registration-repository-ref" type="xs:token"> <xs:attribute name="relying-party-registration-repository-ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2091,7 +2091,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="saml2-logout.attlist"> <xs:attributeGroup name="saml2-logout.attlist">
<xs:attribute name="logout-url" type="xs:token"> <xs:attribute name="logout-url" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2544,7 +2544,7 @@
</xs:simpleType> </xs:simpleType>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="http-basic.attlist"> <xs:attributeGroup name="http-basic.attlist">
<xs:attribute name="entry-point-ref" type="xs:token"> <xs:attribute name="entry-point-ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2577,7 +2577,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="session-management.attlist"> <xs:attributeGroup name="session-management.attlist">
<xs:attribute name="session-fixation-protection"> <xs:attribute name="session-fixation-protection">
<xs:annotation> <xs:annotation>
@ -2633,7 +2633,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="concurrency-control.attlist"> <xs:attributeGroup name="concurrency-control.attlist">
<xs:attribute name="max-sessions" type="xs:token"> <xs:attribute name="max-sessions" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2680,7 +2680,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="remember-me.attlist"> <xs:attributeGroup name="remember-me.attlist">
<xs:attribute name="key" type="xs:token"> <xs:attribute name="key" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2778,7 +2778,7 @@
<xs:attributeGroup name="remember-me-data-source-ref"> <xs:attributeGroup name="remember-me-data-source-ref">
<xs:attributeGroup ref="security:data-source-ref"/> <xs:attributeGroup ref="security:data-source-ref"/>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="anonymous.attlist"> <xs:attributeGroup name="anonymous.attlist">
<xs:attribute name="key" type="xs:token"> <xs:attribute name="key" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2811,8 +2811,8 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="http-port"> <xs:attributeGroup name="http-port">
<xs:attribute name="http" use="required" type="xs:token"> <xs:attribute name="http" use="required" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2829,7 +2829,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="x509.attlist"> <xs:attributeGroup name="x509.attlist">
<xs:attribute name="subject-principal-regex" type="xs:token"> <xs:attribute name="subject-principal-regex" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2966,7 +2966,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="ap.attlist"> <xs:attributeGroup name="ap.attlist">
<xs:attribute name="ref" type="xs:token"> <xs:attribute name="ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -3018,7 +3018,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="user.attlist"> <xs:attributeGroup name="user.attlist">
<xs:attribute name="name" use="required" type="xs:token"> <xs:attribute name="name" use="required" type="xs:token">
<xs:annotation> <xs:annotation>
@ -3720,6 +3720,7 @@
<xs:simpleType name="named-security-filter"> <xs:simpleType name="named-security-filter">
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="FIRST"/> <xs:enumeration value="FIRST"/>
<xs:enumeration value="DISABLE_ENCODE_URL_FILTER"/>
<xs:enumeration value="CHANNEL_FILTER"/> <xs:enumeration value="CHANNEL_FILTER"/>
<xs:enumeration value="SECURITY_CONTEXT_FILTER"/> <xs:enumeration value="SECURITY_CONTEXT_FILTER"/>
<xs:enumeration value="CONCURRENT_SESSION_FILTER"/> <xs:enumeration value="CONCURRENT_SESSION_FILTER"/>
@ -3759,4 +3760,4 @@
<xs:enumeration value="LAST"/> <xs:enumeration value="LAST"/>
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:schema> </xs:schema>

View File

@ -53,7 +53,7 @@ public class FilterOrderRegistrationTests {
@Test @Test
public void putWhenPredefinedFilterThenDoesNotOverride() { public void putWhenPredefinedFilterThenDoesNotOverride() {
int position = 100; int position = 200;
Integer predefinedFilterOrderBefore = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class); Integer predefinedFilterOrderBefore = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class);
this.filterOrderRegistration.put(MyFilter.class, position); this.filterOrderRegistration.put(MyFilter.class, position);
Integer myFilterOrder = this.filterOrderRegistration.getOrder(MyFilter.class); Integer myFilterOrder = this.filterOrderRegistration.getOrder(MyFilter.class);

View File

@ -38,6 +38,7 @@ import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy; import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy; import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
@ -51,19 +52,26 @@ import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 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.httpBasic;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -295,6 +303,46 @@ public class SessionManagementConfigurerTests {
verifyNoInteractions(SessionRegistryTwoBeansConfig.SESSION_REGISTRY_TWO); verifyNoInteractions(SessionRegistryTwoBeansConfig.SESSION_REGISTRY_TWO);
} }
@Test
public void whenEnableSessionUrlRewritingTrueThenEncodeNotInvoked() throws Exception {
this.spring.register(EnableUrlRewriteConfig.class).autowire();
// @formatter:off
this.mvc = MockMvcBuilders.webAppContextSetup(this.spring.getContext())
.addFilters((request, response, chain) -> {
HttpServletResponse responseToSpy = spy((HttpServletResponse) response);
chain.doFilter(request, responseToSpy);
verify(responseToSpy, atLeastOnce()).encodeRedirectURL(any());
verify(responseToSpy, atLeastOnce()).encodeRedirectUrl(any());
verify(responseToSpy, atLeastOnce()).encodeURL(any());
verify(responseToSpy, atLeastOnce()).encodeUrl(any());
})
.apply(springSecurity())
.build();
// @formatter:on
this.mvc.perform(get("/")).andExpect(content().string("encoded"));
}
@Test
public void whenDefaultThenEncodeNotInvoked() throws Exception {
this.spring.register(DefaultUrlRewriteConfig.class).autowire();
// @formatter:off
this.mvc = MockMvcBuilders.webAppContextSetup(this.spring.getContext())
.addFilters((request, response, chain) -> {
HttpServletResponse responseToSpy = spy((HttpServletResponse) response);
chain.doFilter(request, responseToSpy);
verify(responseToSpy, never()).encodeRedirectURL(any());
verify(responseToSpy, never()).encodeRedirectUrl(any());
verify(responseToSpy, never()).encodeURL(any());
verify(responseToSpy, never()).encodeUrl(any());
})
.apply(springSecurity())
.build();
// @formatter:on
this.mvc.perform(get("/")).andExpect(content().string("encoded"));
}
@EnableWebSecurity @EnableWebSecurity
static class SessionManagementRequestCacheConfig extends WebSecurityConfigurerAdapter { static class SessionManagementRequestCacheConfig extends WebSecurityConfigurerAdapter {
@ -569,4 +617,49 @@ public class SessionManagementConfigurerTests {
} }
@EnableWebSecurity
static class DefaultUrlRewriteConfig {
@Bean
DefaultSecurityFilterChain configure(HttpSecurity http) throws Exception {
return http.build();
}
@Bean
EncodesUrls encodesUrls() {
return new EncodesUrls();
}
}
@EnableWebSecurity
static class EnableUrlRewriteConfig {
@Bean
DefaultSecurityFilterChain configure(HttpSecurity http) throws Exception {
http.sessionManagement((sessions) -> sessions.enableSessionUrlRewriting(true));
return http.build();
}
@Bean
EncodesUrls encodesUrls() {
return new EncodesUrls();
}
}
@RestController
static class EncodesUrls {
@RequestMapping("/")
String encoded(HttpServletResponse response) {
response.encodeURL("/foo");
response.encodeUrl("/foo");
response.encodeRedirectURL("/foo");
response.encodeRedirectUrl("/foo");
return "encoded";
}
}
} }

View File

@ -103,6 +103,7 @@ import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.DisableEncodeUrlFilter;
import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@ -121,6 +122,8 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; 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.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@ -540,6 +543,28 @@ public class MiscHttpConfigTests {
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/login"); assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/login");
} }
@Test
public void configureWhenUsingDisableUrlRewritingAndCustomRepositoryThenRedirectIsNotEncodedByResponse()
throws IOException, ServletException {
this.spring.configLocations(xml("DisableUrlRewriting-NullSecurityContextRepository")).autowire();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse responseToSpy = spy(new MockHttpServletResponse());
FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class);
proxy.doFilter(request, responseToSpy, (req, resp) -> {
HttpServletResponse httpResponse = (HttpServletResponse) resp;
httpResponse.encodeUrl("/");
httpResponse.encodeURL("/");
httpResponse.encodeRedirectUrl("/");
httpResponse.encodeRedirectURL("/");
httpResponse.getWriter().write("encodeRedirect");
});
verify(responseToSpy, never()).encodeRedirectURL(any());
verify(responseToSpy, never()).encodeRedirectUrl(any());
verify(responseToSpy, never()).encodeURL(any());
verify(responseToSpy, never()).encodeUrl(any());
assertThat(responseToSpy.getContentAsString()).isEqualTo("encodeRedirect");
}
@Test @Test
public void configureWhenUserDetailsServiceInParentContextThenLocatesSuccessfully() { public void configureWhenUserDetailsServiceInParentContextThenLocatesSuccessfully() {
assertThatExceptionOfType(BeansException.class).isThrownBy( assertThatExceptionOfType(BeansException.class).isThrownBy(
@ -755,6 +780,7 @@ public class MiscHttpConfigTests {
private void assertThatFiltersMatchExpectedAutoConfigList(String url) { private void assertThatFiltersMatchExpectedAutoConfigList(String url) {
Iterator<Filter> filters = getFilters(url).iterator(); Iterator<Filter> filters = getFilters(url).iterator();
assertThat(filters.next()).isInstanceOf(DisableEncodeUrlFilter.class);
assertThat(filters.next()).isInstanceOf(SecurityContextPersistenceFilter.class); assertThat(filters.next()).isInstanceOf(SecurityContextPersistenceFilter.class);
assertThat(filters.next()).isInstanceOf(WebAsyncManagerIntegrationFilter.class); assertThat(filters.next()).isInstanceOf(WebAsyncManagerIntegrationFilter.class);
assertThat(filters.next()).isInstanceOf(HeaderWriterFilter.class); assertThat(filters.next()).isInstanceOf(HeaderWriterFilter.class);

View File

@ -0,0 +1,32 @@
<?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 auto-config="true" disable-url-rewriting="true" security-context-repository-ref="securityContextRepository">
<intercept-url pattern="/**" access="permitAll"/>
</http>
<b:bean id="securityContextRepository" class="org.springframework.security.web.context.NullSecurityContextRepository"/>
<b:import resource="userservice.xml"/>
</b:beans>

View File

@ -253,6 +253,10 @@ The filters are listed in the order in which they occur in the filter chain.
|=== |===
| Alias | Filter Class | Namespace Element or Attribute | Alias | Filter Class | Namespace Element or Attribute
| DISABLE_ENCODE_URL_FILTER
| `DisableEncodeUrlFilter`
| `http@disable-url-rewriting`
| CHANNEL_FILTER | CHANNEL_FILTER
| `ChannelProcessingFilter` | `ChannelProcessingFilter`
| `http/intercept-url@requires-channel` | `http/intercept-url@requires-channel`

View File

@ -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.session;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* Disables encoding URLs using the {@link HttpServletResponse} to prevent including the
* session id in URLs which is not considered URL because the session id can be leaked in
* things like HTTP access logs.
*
* @author Rob Winch
* @since 5.7
*/
public class DisableEncodeUrlFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(request, new DisableEncodeUrlResponseWrapper(response));
}
/**
* Disables URL rewriting for the {@link HttpServletResponse} to prevent including the
* session id in URLs which is not considered URL because the session id can be leaked
* in things like HTTP access logs.
*
* @author Rob Winch
* @since 5.7
*/
private static final class DisableEncodeUrlResponseWrapper extends HttpServletResponseWrapper {
/**
* Constructs a response adaptor wrapping the given response.
* @param response the {@link HttpServletResponse} to be wrapped.
* @throws IllegalArgumentException if the response is null
*/
private DisableEncodeUrlResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public String encodeRedirectUrl(String url) {
return url;
}
@Override
public String encodeRedirectURL(String url) {
return url;
}
@Override
public String encodeUrl(String url) {
return url;
}
@Override
public String encodeURL(String url) {
return url;
}
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.session;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* @author Rob Winch
*/
@ExtendWith(MockitoExtension.class)
class DisableEncodeUrlFilterTests {
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
private DisableEncodeUrlFilter filter = new DisableEncodeUrlFilter();
@Test
void doFilterDisablesEncodeURL() throws Exception {
verifyDoFilterDoesNotInteractWithResponse((httpResponse) -> httpResponse.encodeURL("/"));
}
@Test
void doFilterDisablesEncodeUrl() throws Exception {
verifyDoFilterDoesNotInteractWithResponse((httpResponse) -> httpResponse.encodeUrl("/"));
}
@Test
void doFilterDisablesEncodeRedirectURL() throws Exception {
verifyDoFilterDoesNotInteractWithResponse((httpResponse) -> httpResponse.encodeRedirectURL("/"));
}
@Test
void doFilterDisablesEncodeRedirectUrl() throws Exception {
verifyDoFilterDoesNotInteractWithResponse((httpResponse) -> httpResponse.encodeRedirectUrl("/"));
}
private void verifyDoFilterDoesNotInteractWithResponse(Consumer<HttpServletResponse> toInvoke) throws Exception {
this.filter.doFilter(this.request, this.response, (request, response) -> {
HttpServletResponse httpResponse = (HttpServletResponse) response;
toInvoke.accept(httpResponse);
});
verifyNoInteractions(this.response);
}
}