使用 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 来获取SecureChannelProcessorInsecureChannelProcessor,然后在它们上设置您自己的入口点。这样,您仍然可以在其他所有内容上使用默认值。

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?

spring 3.0.5 库 jars