mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-06 13:29:13 +00:00
SEC-818: Changed redirect URL validation to ignore potential property placeholders at parsing time and report a warning through the parser context rather than an error. Also validated the URLs in the beans themselves using Asserts, so an exception will occur later when the beans have been created rather than while assembling the bean definitions.
This commit is contained in:
parent
5cf0c84e2f
commit
8ad2d681ab
@ -21,6 +21,7 @@ import org.springframework.security.ui.FilterChainOrder;
|
|||||||
import org.springframework.security.ui.SpringSecurityFilter;
|
import org.springframework.security.ui.SpringSecurityFilter;
|
||||||
import org.springframework.security.ui.logout.LogoutHandler;
|
import org.springframework.security.ui.logout.LogoutHandler;
|
||||||
import org.springframework.security.ui.logout.SecurityContextLogoutHandler;
|
import org.springframework.security.ui.logout.SecurityContextLogoutHandler;
|
||||||
|
import org.springframework.security.util.UrlUtils;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ public class ConcurrentSessionFilter extends SpringSecurityFilter implements Ini
|
|||||||
|
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
Assert.notNull(sessionRegistry, "SessionRegistry required");
|
Assert.notNull(sessionRegistry, "SessionRegistry required");
|
||||||
|
Assert.isTrue(UrlUtils.isValidRedirectUrl(expiredUrl), expiredUrl + " isn't a valid redirect URL");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||||
|
@ -16,6 +16,7 @@ import org.springframework.beans.factory.xml.ParserContext;
|
|||||||
import org.springframework.security.afterinvocation.AfterInvocationProviderManager;
|
import org.springframework.security.afterinvocation.AfterInvocationProviderManager;
|
||||||
import org.springframework.security.providers.ProviderManager;
|
import org.springframework.security.providers.ProviderManager;
|
||||||
import org.springframework.security.userdetails.UserDetailsService;
|
import org.springframework.security.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.util.UrlUtils;
|
||||||
import org.springframework.security.vote.AffirmativeBased;
|
import org.springframework.security.vote.AffirmativeBased;
|
||||||
import org.springframework.security.vote.AuthenticatedVoter;
|
import org.springframework.security.vote.AuthenticatedVoter;
|
||||||
import org.springframework.security.vote.RoleVoter;
|
import org.springframework.security.vote.RoleVoter;
|
||||||
@ -180,12 +181,12 @@ public abstract class ConfigUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the value of an XML attribute which represents a redirect URL.
|
* Checks the value of an XML attribute which represents a redirect URL.
|
||||||
* If not empty or starting with "/" or "http" it will raise an error.
|
* If not empty or starting with "$" (potential placeholder), "/" or "http" it will raise an error.
|
||||||
*/
|
*/
|
||||||
static void validateHttpRedirect(String url, ParserContext pc, Object source) {
|
static void validateHttpRedirect(String url, ParserContext pc, Object source) {
|
||||||
if (!StringUtils.hasText(url) || url.startsWith("/") || url.toLowerCase().startsWith("http")) {
|
if (UrlUtils.isValidRedirectUrl(url) || url.startsWith("$")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pc.getReaderContext().error(url + " is not a valid redirect URL (must start with '/' or http(s))", source);
|
pc.getReaderContext().warning(url + " is not a valid redirect URL (must start with '/' or http(s))", source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import org.springframework.security.AuthenticationException;
|
|||||||
import org.springframework.security.AuthenticationManager;
|
import org.springframework.security.AuthenticationManager;
|
||||||
import org.springframework.security.util.RedirectUtils;
|
import org.springframework.security.util.RedirectUtils;
|
||||||
import org.springframework.security.util.SessionUtils;
|
import org.springframework.security.util.SessionUtils;
|
||||||
|
import org.springframework.security.util.UrlUtils;
|
||||||
|
|
||||||
import org.springframework.security.context.SecurityContextHolder;
|
import org.springframework.security.context.SecurityContextHolder;
|
||||||
|
|
||||||
@ -43,10 +44,6 @@ import org.springframework.util.Assert;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
@ -215,8 +212,11 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
|
|||||||
|
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified");
|
Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified");
|
||||||
|
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL");
|
||||||
Assert.hasLength(defaultTargetUrl, "defaultTargetUrl must be specified");
|
Assert.hasLength(defaultTargetUrl, "defaultTargetUrl must be specified");
|
||||||
|
Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultTargetUrl), defaultTargetUrl + " isn't a valid redirect URL");
|
||||||
// Assert.hasLength(authenticationFailureUrl, "authenticationFailureUrl must be specified");
|
// Assert.hasLength(authenticationFailureUrl, "authenticationFailureUrl must be specified");
|
||||||
|
Assert.isTrue(UrlUtils.isValidRedirectUrl(authenticationFailureUrl), authenticationFailureUrl + " isn't a valid redirect URL");
|
||||||
Assert.notNull(authenticationManager, "authenticationManager must be specified");
|
Assert.notNull(authenticationManager, "authenticationManager must be specified");
|
||||||
Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
|
Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
|
||||||
Assert.notNull(targetUrlResolver, "targetUrlResolver cannot be null");
|
Assert.notNull(targetUrlResolver, "targetUrlResolver cannot be null");
|
||||||
|
@ -54,7 +54,7 @@ public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
|
|||||||
//~ Methods ========================================================================================================
|
//~ Methods ========================================================================================================
|
||||||
|
|
||||||
public void handle(ServletRequest request, ServletResponse response, AccessDeniedException accessDeniedException)
|
public void handle(ServletRequest request, ServletResponse response, AccessDeniedException accessDeniedException)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
if (errorPage != null) {
|
if (errorPage != null) {
|
||||||
// Put exception into request scope (perhaps of use to a view)
|
// Put exception into request scope (perhaps of use to a view)
|
||||||
((HttpServletRequest) request).setAttribute(SPRING_SECURITY_ACCESS_DENIED_EXCEPTION_KEY,
|
((HttpServletRequest) request).setAttribute(SPRING_SECURITY_ACCESS_DENIED_EXCEPTION_KEY,
|
||||||
@ -80,7 +80,7 @@ public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
|
|||||||
*/
|
*/
|
||||||
public void setErrorPage(String errorPage) {
|
public void setErrorPage(String errorPage) {
|
||||||
if ((errorPage != null) && !errorPage.startsWith("/")) {
|
if ((errorPage != null) && !errorPage.startsWith("/")) {
|
||||||
throw new IllegalArgumentException("ErrorPage must begin with '/'");
|
throw new IllegalArgumentException("errorPage must begin with '/'");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.errorPage = errorPage;
|
this.errorPage = errorPage;
|
||||||
|
@ -26,6 +26,7 @@ import org.springframework.security.Authentication;
|
|||||||
import org.springframework.security.ui.SpringSecurityFilter;
|
import org.springframework.security.ui.SpringSecurityFilter;
|
||||||
import org.springframework.security.ui.FilterChainOrder;
|
import org.springframework.security.ui.FilterChainOrder;
|
||||||
import org.springframework.security.util.RedirectUtils;
|
import org.springframework.security.util.RedirectUtils;
|
||||||
|
import org.springframework.security.util.UrlUtils;
|
||||||
import org.springframework.security.context.SecurityContextHolder;
|
import org.springframework.security.context.SecurityContextHolder;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@ -58,6 +59,7 @@ public class LogoutFilter extends SpringSecurityFilter {
|
|||||||
public LogoutFilter(String logoutSuccessUrl, LogoutHandler[] handlers) {
|
public LogoutFilter(String logoutSuccessUrl, LogoutHandler[] handlers) {
|
||||||
Assert.notEmpty(handlers, "LogoutHandlers are required");
|
Assert.notEmpty(handlers, "LogoutHandlers are required");
|
||||||
this.logoutSuccessUrl = logoutSuccessUrl;
|
this.logoutSuccessUrl = logoutSuccessUrl;
|
||||||
|
Assert.isTrue(UrlUtils.isValidRedirectUrl(logoutSuccessUrl), logoutSuccessUrl + " isn't a valid redirect URL");
|
||||||
this.handlers = handlers;
|
this.handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +163,7 @@ public class LogoutFilter extends SpringSecurityFilter {
|
|||||||
|
|
||||||
public void setFilterProcessesUrl(String filterProcessesUrl) {
|
public void setFilterProcessesUrl(String filterProcessesUrl) {
|
||||||
Assert.hasText(filterProcessesUrl, "FilterProcessesUrl required");
|
Assert.hasText(filterProcessesUrl, "FilterProcessesUrl required");
|
||||||
|
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL");
|
||||||
this.filterProcessesUrl = filterProcessesUrl;
|
this.filterProcessesUrl = filterProcessesUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ package org.springframework.security.util;
|
|||||||
import org.springframework.security.intercept.web.FilterInvocation;
|
import org.springframework.security.intercept.web.FilterInvocation;
|
||||||
|
|
||||||
import org.springframework.security.ui.savedrequest.SavedRequest;
|
import org.springframework.security.ui.savedrequest.SavedRequest;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
@ -117,4 +118,8 @@ public final class UrlUtils {
|
|||||||
return buildRequestUrl(sr.getServletPath(), sr.getRequestURI(), sr.getContextPath(), sr.getPathInfo(),
|
return buildRequestUrl(sr.getServletPath(), sr.getRequestURI(), sr.getContextPath(), sr.getPathInfo(),
|
||||||
sr.getQueryString());
|
sr.getQueryString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isValidRedirectUrl(String url) {
|
||||||
|
return !StringUtils.hasText(url) || url.startsWith("/") || url.toLowerCase().startsWith("http");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import java.util.List;
|
|||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.beans.factory.BeanCreationException;
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
|
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||||
import org.springframework.context.support.AbstractXmlApplicationContext;
|
import org.springframework.context.support.AbstractXmlApplicationContext;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
@ -174,7 +175,7 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "alwaysUseDefaultTargetUrl"));
|
assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "alwaysUseDefaultTargetUrl"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=BeanDefinitionParsingException.class)
|
@Test(expected=BeanCreationException.class)
|
||||||
public void invalidLoginPageIsDetected() throws Exception {
|
public void invalidLoginPageIsDetected() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
"<http>" +
|
"<http>" +
|
||||||
@ -182,7 +183,7 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
"</http>" + AUTH_PROVIDER_XML);
|
"</http>" + AUTH_PROVIDER_XML);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=BeanDefinitionParsingException.class)
|
@Test(expected=BeanCreationException.class)
|
||||||
public void invalidDefaultTargetUrlIsDetected() throws Exception {
|
public void invalidDefaultTargetUrlIsDetected() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
"<http>" +
|
"<http>" +
|
||||||
@ -190,7 +191,7 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
"</http>" + AUTH_PROVIDER_XML);
|
"</http>" + AUTH_PROVIDER_XML);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=BeanDefinitionParsingException.class)
|
@Test(expected=BeanCreationException.class)
|
||||||
public void invalidLogoutUrlIsDetected() throws Exception {
|
public void invalidLogoutUrlIsDetected() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
"<http>" +
|
"<http>" +
|
||||||
@ -199,7 +200,7 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
"</http>" + AUTH_PROVIDER_XML);
|
"</http>" + AUTH_PROVIDER_XML);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=BeanDefinitionParsingException.class)
|
@Test(expected=BeanCreationException.class)
|
||||||
public void invalidLogoutSuccessUrlIsDetected() throws Exception {
|
public void invalidLogoutSuccessUrlIsDetected() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
"<http>" +
|
"<http>" +
|
||||||
@ -265,7 +266,7 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
assertEquals("/access-denied", FieldUtils.getFieldValue(etf, "accessDeniedHandler.errorPage"));
|
assertEquals("/access-denied", FieldUtils.getFieldValue(etf, "accessDeniedHandler.errorPage"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=BeanDefinitionParsingException.class)
|
@Test(expected=BeanDefinitionStoreException.class)
|
||||||
public void invalidAccessDeniedUrlIsDetected() throws Exception {
|
public void invalidAccessDeniedUrlIsDetected() throws Exception {
|
||||||
setContext("<http auto-config='true' access-denied-page='noLeadingSlash'/>" + AUTH_PROVIDER_XML);
|
setContext("<http auto-config='true' access-denied-page='noLeadingSlash'/>" + AUTH_PROVIDER_XML);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user