瀏覽代碼

Add DisableUrlRewritingFilter

Closes gh-11084
Rob Winch 3 年之前
父節點
當前提交
39b0620a84
共有 14 個文件被更改,包括 375 次插入36 次删除
  1. 2 0
      config/src/main/java/org/springframework/security/config/annotation/web/HttpSecurityBuilder.java
  2. 2 0
      config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java
  3. 4 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java
  4. 19 5
      config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
  5. 2 0
      config/src/main/java/org/springframework/security/config/http/SecurityFilters.java
  6. 1 1
      config/src/main/resources/org/springframework/security/config/spring-security-5.7.rnc
  7. 30 29
      config/src/main/resources/org/springframework/security/config/spring-security-5.7.xsd
  8. 1 1
      config/src/test/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistrationTests.java
  9. 93 0
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java
  10. 26 0
      config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java
  11. 32 0
      config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-DisableUrlRewriting-NullSecurityContextRepository.xml
  12. 4 0
      docs/modules/ROOT/pages/servlet/configuration/xml-namespace.adoc
  13. 86 0
      web/src/main/java/org/springframework/security/web/session/DisableEncodeUrlFilter.java
  14. 73 0
      web/src/test/java/org/springframework/security/web/session/DisableEncodeUrlFilterTests.java

+ 2 - 0
config/src/main/java/org/springframework/security/config/annotation/web/HttpSecurityBuilder.java

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

+ 2 - 0
config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java

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

+ 4 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java

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

+ 19 - 5
config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java

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

+ 2 - 0
config/src/main/java/org/springframework/security/config/http/SecurityFilters.java

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

+ 1 - 1
config/src/main/resources/org/springframework/security/config/spring-security-5.7.rnc

@@ -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.
 	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"

+ 30 - 29
config/src/main/resources/org/springframework/security/config/spring-security-5.7.xsd

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

+ 1 - 1
config/src/test/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistrationTests.java

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

+ 93 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java

@@ -38,6 +38,7 @@ import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.session.SessionRegistry;
 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.CompositeSessionAuthenticationStrategy;
 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.MvcResult;
 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.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.atLeastOnce;
 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.verifyNoInteractions;
 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.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.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.status;
 
@@ -295,6 +303,46 @@ public class SessionManagementConfigurerTests {
 		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
 	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";
+		}
+
+	}
+
 }

+ 26 - 0
config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java

@@ -103,6 +103,7 @@ import org.springframework.security.web.header.HeaderWriterFilter;
 import org.springframework.security.web.savedrequest.RequestCache;
 import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
 import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
+import org.springframework.security.web.session.DisableEncodeUrlFilter;
 import org.springframework.security.web.session.SessionManagementFilter;
 import org.springframework.test.util.ReflectionTestUtils;
 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.Mockito.atLeastOnce;
 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.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
 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");
 	}
 
+	@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
 	public void configureWhenUserDetailsServiceInParentContextThenLocatesSuccessfully() {
 		assertThatExceptionOfType(BeansException.class).isThrownBy(
@@ -755,6 +780,7 @@ public class MiscHttpConfigTests {
 
 	private void assertThatFiltersMatchExpectedAutoConfigList(String url) {
 		Iterator<Filter> filters = getFilters(url).iterator();
+		assertThat(filters.next()).isInstanceOf(DisableEncodeUrlFilter.class);
 		assertThat(filters.next()).isInstanceOf(SecurityContextPersistenceFilter.class);
 		assertThat(filters.next()).isInstanceOf(WebAsyncManagerIntegrationFilter.class);
 		assertThat(filters.next()).isInstanceOf(HeaderWriterFilter.class);

+ 32 - 0
config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-DisableUrlRewriting-NullSecurityContextRepository.xml

@@ -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>

+ 4 - 0
docs/modules/ROOT/pages/servlet/configuration/xml-namespace.adoc

@@ -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
 
+| DISABLE_ENCODE_URL_FILTER
+| `DisableEncodeUrlFilter`
+| `http@disable-url-rewriting`
+
 |  CHANNEL_FILTER
 | `ChannelProcessingFilter`
 | `http/intercept-url@requires-channel`

+ 86 - 0
web/src/main/java/org/springframework/security/web/session/DisableEncodeUrlFilter.java

@@ -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;
+		}
+
+	}
+
+}

+ 73 - 0
web/src/test/java/org/springframework/security/web/session/DisableEncodeUrlFilterTests.java

@@ -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);
+	}
+
+}