使用 Spring Security 3.0.5 覆盖 ChannelProcessingFilter 不起作用
Posted
技术标签:
【中文标题】使用 Spring Security 3.0.5 覆盖 ChannelProcessingFilter 不起作用【英文标题】:Override the ChannelProcessingFilter with Spring Security 3.0.5 does not work 【发布时间】:2012-01-28 08:05:30 【问题描述】:通道处理器的默认行为是执行 sendRedirect(使用 302 代码临时重定向)。我需要更改此行为,以便完成永久 (301) 重定向而不是 302 重定向。我尝试执行以下操作:
通过扩展 ChannelProcessingFilter 创建自定义 ChannelProcessingFilter:
public class MyChannelProcessingFilter extends ChannelProcessingFilter
//No implementation, I needed this to just make sure that a custom filter is created and I can configure it as a custom filter in the xml file.
通过扩展 AbstractRetryEntryPoint 创建自定义入口点
public class RetryWithHttpsEntryPoint extends org.springframework.security.web.access.channel.AbstractRetryEntryPoint
private PortResolver portResolver = new PortResolverImpl();
private final String scheme ="https://";
/** The standard port for the scheme (80 for http, 443 for https) */
private final int standardPort = 443;
public RetryWithHttpsEntryPoint()
super("https://", 443);
@Override
public void commence(HttpServletRequest request, HttpServletResponse res) throws IOException, ServletException
String queryString = request.getQueryString();
String redirectUrl = request.getRequestURI() + ((queryString == null) ? "" : ("?" + queryString));
Integer currentPort = new Integer(portResolver.getServerPort(request));
Integer redirectPort = getMappedPort(currentPort);
if (redirectPort != null)
boolean includePort = redirectPort.intValue() != standardPort;
redirectUrl = scheme + request.getServerName() + ((includePort) ? (":" + redirectPort) : "") + redirectUrl;
if (logger.isDebugEnabled())
logger.debug("Redirecting to: " + redirectUrl);
res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
res.setHeader("Location", redirectUrl);
res.setHeader("Connection", "close");
protected Integer getMappedPort(Integer mapFromPort)
return getPortMapper().lookupHttpsPort(mapFromPort);
在 applicationContext-security.xml 文件中进行相同的配置。我将完整的 xml 文件供您参考(删除不需要的部分。如果您需要其他部分,请告诉我)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<security:http auto-config="false"
entry-point-ref="authenticationProcessingFilterEntryPoint"
access-decision-manager-ref="accessDecisionManager" >
<security:intercept-url pattern="/activ8/protectedCheckEligibility.html**" access="user" requires-channel="https"/>
<security:intercept-url pattern="/siteMap.html" access="ROLE_ANONYMOUS,user,admin" requires-channel="http"/>
<security:intercept-url pattern="/privacyPolicy.html" access="ROLE_ANONYMOUS,user,admin" requires-channel="http"/>
<!-- other urls configured over here -->
<security:intercept-url pattern="/*.jsp" access="ROLE_ANONYMOUS,admin,user" requires-channel="https"/>
<security:intercept-url pattern="/**/*.html**" access="ROLE_ANONYMOUS,user,admin" requires-channel="https"/>
<security:intercept-url pattern="/fb_activities.html**" access="parent" />
<security:remember-me key="appfuseRocks" />
<security:custom-filter position="SWITCH_USER_FILTER" ref="careSwitchUserProcessingFilter"/>
<security:custom-filter position="FORM_LOGIN_FILTER" ref="myCustomAuthenticationProcessingFilter"/>
<!-- configured the custom channel filter over here -->
<security:custom-filter position="CHANNEL_FILTER" ref="myChannelProcessingFilter"/>
</security:http>
<bean id="myChannelProcessingFilter" class="com.my.webapp.filter.myChannelProcessingFilter">
<property name="channelDecisionManager" ref="channelDecisionManager" />
<property name="securityMetadataSource">
<security:filter-security-metadata-source path-type="ant">
<security:intercept-url pattern="/**" access="REQUIRES_INSECURE_CHANNEL" />
</security:filter-security-metadata-source>
</property>
</bean>
<bean id="channelDecisionManager" class="org.springframework.security.web.access.channel.ChannelDecisionManagerImpl">
<property name="channelProcessors">
<list>
<ref bean="secureChannelProcessor"/>
</list>
</property>
</bean>
<bean id="secureChannelProcessor" class="org.springframework.security.web.access.channel.SecureChannelProcessor">
<property name="entryPoint" ref="secureChannelEntryPoint"/>
<!-- <property name="portMapper" ref="portMapper" /> -->
<property name="secureKeyword" value="REQUIRES_SECURE_CHANNEL"/>
</bean>
<bean id="secureChannelEntryPoint" class="com.my.webapp.filter.RetryWithHttpsEntryPoint"/>
<!-- lot of other configuratons... removed -->
</beans>
我在尝试运行我的 tomcat 时遇到以下错误:
错误 2011-12-26 21:13:21,569 [ina].[localhost].[/]]:向 com.kajeet.webapp.listener.StartupListener 类的侦听器实例发送上下文初始化事件的异常 org.springframework.beans.factory.parsing.BeanDefinitionParsingException:配置问题:过滤bean''和'根bean:类[org.springframework.security.web.access.channel.ChannelProcessingFilter];范围=;摘要=假;懒惰初始化=假;自动线模式=0;依赖检查=0;自动接线候选=真;主要=假;工厂BeanName=空;工厂方法名=空;初始化方法名=空; destroyMethodName=null' 具有相同的 'order' 值。使用自定义过滤器时,请确保位置不与默认过滤器冲突。或者,您可以通过删除相应的子元素并避免使用 . 违规资源:ServletContext 资源 [/WEB-INF/applicationContext-security.xml] 在 org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68) 在 org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85) 在 org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:72) 在 org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.checkFilterChainOrder(HttpSecurityBeanDefinitionParser.java:196) 在 org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.parse(HttpSecurityBeanDefinitionParser.java:132) 在 org.springframework.security.config.SecurityNamespaceHandler.parse(SecurityNamespaceHandler.java:86) 在 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1335) 在 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1325) 在 org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:135) 在 org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:93) 在 org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:493) 在 org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390) 在 org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334) 在 org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302) 在 org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143) 在 org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178) 在 org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:149) 在 org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:124) 在 org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:93) 在 org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:130) 在 org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:467) 在 org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:397) 在 org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:276) 在 org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:197) 在 org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:47) 在 com.kajeet.webapp.listener.StartupListener.contextInitialized(StartupListener.java:51) 在 org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3764) 在 org.apache.catalina.core.StandardContext.start(StandardContext.java:4216) 在 org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:760) 在 org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:740) 在 org.apache.catalina.core.StandardHost.addChild(StandardHost.java:544) 在 org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:920) 在 org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:883) 在 org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:492) 在 org.apache.catalina.startup.HostConfig.start(HostConfig.java:1138) 在 org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:311) 在 org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:120) 在 org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1022) 在 org.apache.catalina.core.StandardHost.start(StandardHost.java:736) 在 org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1014) 在 org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443) 在 org.apache.catalina.core.StandardService.start(StandardService.java:448) 在 org.apache.catalina.core.StandardServer.start(StandardServer.java:700) 在 org.apache.catalina.startup.Catalina.start(Catalina.java:552) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 在 java.lang.reflect.Method.invoke(Method.java:597) 在 org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:295) 在 org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:433)我还覆盖了其他过滤器,它不会抱怨这些。这个应用程序之前运行得非常好。我们有这个额外的要求,因此我添加了新的过滤器并遇到了这样的错误。
我尝试的第二种方法只是在 XML 中配置默认的 ChannelProcessingFilter,因为在 Spring 3.0 中会自动调用过滤器,我的印象是我可以在 XML 文件中配置它们,spring 会自动加载它们,但它没有't:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<security:http auto-config="false"
entry-point-ref="authenticationProcessingFilterEntryPoint"
access-decision-manager-ref="accessDecisionManager" >
<security:intercept-url pattern="/activ8/protectedCheckEligibility.html**" access="user" requires-channel="https"/>
<security:intercept-url pattern="/siteMap.html" access="ROLE_ANONYMOUS,user,admin" requires-channel="http"/>
<security:intercept-url pattern="/privacyPolicy.html" access="ROLE_ANONYMOUS,user,admin" requires-channel="http"/>
<!-- other urls configured over here -->
<security:intercept-url pattern="/*.jsp" access="ROLE_ANONYMOUS,admin,user" requires-channel="https"/>
<security:intercept-url pattern="/**/*.html**" access="ROLE_ANONYMOUS,user,admin" requires-channel="https"/>
<security:intercept-url pattern="/fb_activities.html**" access="parent" />
<security:remember-me key="appfuseRocks" />
<security:custom-filter position="SWITCH_USER_FILTER" ref="careSwitchUserProcessingFilter"/>
<security:custom-filter position="FORM_LOGIN_FILTER" ref="myCustomAuthenticationProcessingFilter"/>
</security:http>
<bean id="channelDecisionManager" class="org.springframework.security.securechannel.ChannelDecisionManagerImpl">
<property name="channelProcessors">
<list>
<ref bean="secureChannelProcessor"/>
<ref bean="insecureChannelProcessor"/>
</list>
</property>
</bean>
<bean id="secureChannelProcessor" class="org.springframework.security.web.access.channel.SecureChannelProcessor"/>
<bean id="insecureChannelProcessor" class="org.springframework.security.web.access.channel.InsecureChannelProcessor"/>
<!-- lot of other configuratons... removed -->
</beans>
我们将不胜感激任何帮助。我不是 Spring 专业人士,但我已经做了一些工作,一两个指针肯定可以帮助我解决这个问题。提前谢谢你
【问题讨论】:
经过进一步研究,我知道这不需要扩展 ChannelProcessingFilter,我发现的问题是 SecureChannelProcessor 被初始化了两次,一次使用默认值,第二次使用自定义值,即XML 的一部分。应用规则时,它只使用具有默认值的规则。好像我在某处遗漏了一个非常小的链接。 【参考方案1】:解决方案:
问题是我们不能同时拥有 security:http 和 myChannelProcessingFilter(我已经覆盖的那个)来处理 security:intercept-url 的访问参数,因此我删除了 http 标签并添加了访问我希望它处理的 myChannelProcessingFilter 中的东西。解析它的 XML 如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!--
The http element responsible for creating a FilterChainProxy and the filter beans which it uses.
Common problems like incorrect filter ordering are no longer an issue as the filter positions are predefined.
-->
<security:http auto-config="false"
entry-point-ref="authenticationProcessingFilterEntryPoint"
access-decision-manager-ref="accessDecisionManager" >
<security:custom-filter position="CHANNEL_FILTER" ref="channelProcessingFilter"/>
<security:intercept-url pattern="/*.html*" access="ROLE_ANONYMOUS,admin,user" />
<security:intercept-url pattern="/*.jsp" access="ROLE_ANONYMOUS,admin,user" />
<security:intercept-url pattern="/**/*.html**" access="ROLE_ANONYMOUS,user,admin" />
</security:http>
<bean id="channelProcessingFilter" class="org.springframework.security.web.access.channel.ChannelProcessingFilter">
<property name="channelDecisionManager" ref="channelDecisionManager"/>
<property name="securityMetadataSource">
<security:filter-security-metadata-source path-type="ant">
<security:intercept-url pattern="/*.jsp**" access="REQUIRES_SECURE_CHANNEL" />
<security:intercept-url pattern="/**/*.html**" access="REQUIRES_SECURE_CHANNEL" />
</security:filter-security-metadata-source>
</property>
</bean>
<bean id="channelDecisionManager" class="org.springframework.security.web.access.channel.ChannelDecisionManagerImpl">
<property name="channelProcessors">
<list>
<ref bean="secureProcessor"/>
<ref bean="insecureProcessor"/>
</list>
</property>
</bean>
<bean id="secureProcessor" class="org.springframework.security.web.access.channel.SecureChannelProcessor" >
<property name="entryPoint" ref="retryWithHttps"/>
</bean>
<bean id="insecureProcessor" class="org.springframework.security.web.access.channel.InsecureChannelProcessor">
<property name="entryPoint" ref="retryWithHttp"/>
</bean>
<bean id="retryWithHttps" class="com.my.webapp.filter.RetryWithHttpsEntryPoint" />
<bean id="retryWithHttp" class="com.my.webapp.filter.RetryWithHttpEntryPoint" />
</beans>
【讨论】:
【参考方案2】:我找到了另一种方法来实现同样的事情,而代码和复杂性要少得多。您可以简单地使用BeanPostProcessor
来获取SecureChannelProcessor
和InsecureChannelProcessor
,然后在它们上设置您自己的入口点。这样,您仍然可以在其他所有内容上使用默认值。
BeanPostProcessor:
@Component
public class ChannelProcessorsPostProcessor implements BeanPostProcessor
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException
if (bean instanceof SecureChannelProcessor) ((SecureChannelProcessor)bean).setEntryPoint(new MyEntryRetryPoint("https://", 443));
else if (bean instanceof InsecureChannelProcessor) ((InsecureChannelProcessor)bean).setEntryPoint(new MyEntryRetryPoint("http://", 80));
return bean;
@Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException
return bean;
【讨论】:
【参考方案3】:我觉得还是写重定向策略比较好:
@Component
public class PermanentRedirectStrategy implements RedirectStrategy
private boolean contextRelative;
@Override
public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location", response.encodeRedirectURL(calculateRedirectUrl(request.getContextPath(), url)));
/**
* Unfortunately DefaultRedirectStrategy.calculateRedirectUrl is private
* If this weren't the case, we could extend this class from DefaultRedirectStrategy
* to use its method directly without copying it
*/
private String calculateRedirectUrl(String contextPath, String url)
if (!UrlUtils.isAbsoluteUrl(url))
if (contextRelative)
return url;
else
return contextPath + url;
// Full URL, including http(s)://
if (!contextRelative)
return url;
// Calculate the relative URL from the fully qualified URL, minus the last
// occurence of the scheme and base context
url = url.substring(url.lastIndexOf("://") + 3); // strip off scheme
url = url.substring(url.indexOf(contextPath) + contextPath.length());
if (url.length() > 1 && url.charAt(0) == '/')
url = url.substring(1);
return url;
然后将其设置为现有的入口点:
@Component
public class ChannelProcessorsPostProcessor implements BeanPostProcessor
@Autowired
private RedirectStrategy permanentRedirectStrategy;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
ChannelEntryPoint entryPoint = null;
if (bean instanceof SecureChannelProcessor)
entryPoint = ((SecureChannelProcessor) bean).getEntryPoint();
else if (bean instanceof InsecureChannelProcessor)
entryPoint = ((InsecureChannelProcessor) bean).getEntryPoint();
if (entryPoint != null && AbstractRetryEntryPoint.class.isAssignableFrom(entryPoint.getClass()))
((AbstractRetryEntryPoint) entryPoint).setRedirectStrategy(permanentRedirectStrategy);
return bean;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
return bean;
【讨论】:
以上是关于使用 Spring Security 3.0.5 覆盖 ChannelProcessingFilter 不起作用的主要内容,如果未能解决你的问题,请参考以下文章
Spring Security 3.1 xsd 和 jars 不匹配问题
如何在 JSF 中使用 Spring Security Facelets 标签库
REST 服务的基于 Spring Security 的身份验证
org.springframework.security:org.springframework.security.web:jar:3.0.5.RELEASE 的 POM 丢失,没有可用的依赖信息
Spring Framework,Spring Security - 可以在没有 Spring Framework 的情况下使用 Spring Security?