DelegatingFilterProxy 在没有 Spring Security 的 Spring MVC 应用程序上调用了两次

Posted

技术标签:

【中文标题】DelegatingFilterProxy 在没有 Spring Security 的 Spring MVC 应用程序上调用了两次【英文标题】:DelegatingFilterProxy called twice on Spring MVC application without Spring Security 【发布时间】:2012-06-15 14:45:48 【问题描述】:

我在我的 Spring MVC 应用程序中有一个作为 DelegatingFilterProxy 实现的过滤器,它被调用了两次。我不知道发生了什么事。我确信 Spring 上下文没有被创建两次,因为所有配置都在 root-context.xml 中,而我的 servlet-context.xml 是空的。

我的 web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <display-name>cheapig</display-name>

    <!--Definição do Contexto Global do Container do Spring com recursos (beans) que são compartilhados com
    TODOS os Servlets e Filtros -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>

    <!-- Cria o container do Spring compartilhado com todos os Servlets e Filtros -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <listener>
        <display-name>sessionListener</display-name>
        <listener-class>br.com.cheapig.util.SessionListener</listener-class>
    </listener>

    <!-- Filtro para controlar acesso -->
    <filter>
     <filter-name>cheapigFilter</filter-name>
     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>      
    </filter>

    <filter-mapping>
      <filter-name>cheapigFilter</filter-name>
      <url-pattern>/*</url-pattern>

    </filter-mapping>

    <!-- Definição do Servlet que processa todos os requests da aplicação. Como se está utilizando
    o Framework Spring, o servlet é o DispatcherServlet. -->
    <servlet>
        <servlet-name>cheapig</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/cheapig/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Mapeamento dos Servlets e URLs -->     
    <servlet-mapping>
        <servlet-name>cheapig</servlet-name>
        <url-pattern>/cheapig/*</url-pattern>
    </servlet-mapping>

    <error-page>
        <error-code>404</error-code>
        <location>/WEB-INF/view/util/layout_404.jsp</location>
    </error-page>

    <error-page>
        <error-code>405</error-code>
        <location>/WEB-INF/view/util/layout_405.jsp</location>
    </error-page>

    <error-page>
        <error-code>500</error-code>
        <location>/WEB-INF/view/util/layout_500.jsp</location>
    </error-page>

    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>

</web-app>

我的根上下文(记住 servlet-context.xml 已被注释):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/aop       
        http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd
        ">
    <mvc:resources mapping="/resources/**" location="/resources/" />

    <!-- Imports user-defined @Controller beans that process client requests -->
    <import resource="cheapig/controllers.xml" />
    <import resource="cheapig/hibernatemysql5.xml"/>    
    <import resource="cheapig/integracaoTiles2.xml"/>
    <import resource="cheapig/servicoEmail.xml"/>

    <mvc:annotation-driven/>
    <context:component-scan base-package="br.com.cheapig" />
    <task:annotation-driven/>

    <tx:annotation-driven transaction-manager="transactionManager" />

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
      <property name="sessionFactory" ref="sessionFactory"/>    
    </bean>

    <!-- Root Context: defines shared resources visible to all other web components -->
    <!-- Configurações de Internacionalização -->
    <bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basename" value="classpath:messages" />
        <property name="defaultEncoding" value="latin1" />
    </bean> 

    <bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
        <property name="defaultLocale" value="pt" />
    </bean> 

    <!-- Mapeamento da view "rssViewer" para o bean "rssViewer" -->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
    <bean id="rssViewer" class="br.com.cheapig.servico.CustomRssViewer" />



    <bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- one of the properties available; the maximum file size in bytes -->
        <property name="maxUploadSize" value="3145728" />
        <property name="maxInMemorySize" value="3145728"></property>
    </bean>

    <bean id="velocityEngine"
        class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
        <property name="velocityProperties">
            <value>
                resource.loader=file
                file.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
            </value>
        </property>
    </bean>
    <!-- <bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean"> 
        <property name="velocityProperties"> <value> resource.loader=class class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader 
        </value> </property> <property name="resourceLoaderPath"> <value>/WEB-INF/velocity</value> 
        </property> </bean> -->



    <!-- <bean id="controleAcesso" class="br.com.cheapig.seguranca.ControleAcesso"> 
        <bean id="cheapigFilter" class="br.com.cheapig.seguranca.SegurancaFilter" 
        > <property name="controleAcesso" ref="controleAcesso" /> <property name="localeResolver" 
        ref="localeResolver" /> </bean> -->

</beans>

我的过滤器:

package br.com.cheapig.seguranca;

import java.io.IOException;
import java.util.Date;
import java.util.Locale;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.LocaleResolver;

import br.com.cheapig.controlador.HistoricoVisitasController;
import br.com.cheapig.dominio.HistoricoVisitas;
import br.com.cheapig.dominio.Usuario;
import br.com.cheapig.exception.ControleAcessoException;
import br.com.cheapig.util.CheapigUtil;
import br.com.cheapig.util.ConstantesAmbiente;
import br.com.cheapig.util.ConstantesGenericas;
import br.com.cheapig.util.ConstantesHistoricoVisitas;
import br.com.cheapig.util.ConstantesSessao;
import br.com.cheapig.util.WorkflowUtil;
import br.com.cheapig.workflow.Request;

/**
 * Classe responsável por fazer a filtragem do controle de acesso aos recursos do site
 * 
 * @author Guilherme Macedo
 * @since Apr 30, 2012
 */
@Component(value = "cheapigFilter")
public class SegurancaFilter extends OncePerRequestFilter 

    @Autowired
    private ConstantesAmbiente constantesAmbiente;

    @Autowired
    private LocaleResolver localeResolver;

    @Autowired
    private ControleAcesso controleAcesso;

    @Autowired
    private HistoricoVisitasController historicoVisitasController;

    /**
     * Cria um novo objeto SegurancaFilter
     */
    public SegurancaFilter() 
        super();
    

    /**
     * @see org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain)
     */
    @SuppressWarnings("unchecked")
    @Override
    protected void doFilterInternal(HttpServletRequest pRequest, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException 
        Locale locale = new Locale("pt", "br");
        this.localeResolver.setLocale(pRequest, response, locale);
        LocaleContextHolder.setLocale(locale);
        String path = pRequest.getRequestURI().substring(pRequest.getContextPath().length());
        String uri = pRequest.getRequestURI();

        HttpSession vSession = pRequest.getSession();
        String appId = (String) vSession.getAttribute("facebookAppId");
        if(appId == null || appId.isEmpty())
            vSession.setAttribute("facebookAppId", this.constantesAmbiente.getFacebookAppId()); 
        

        if (path.startsWith("/resources") || path.startsWith("/css")) 
            filterChain.doFilter(pRequest, response); // Goes to default servlet.
            System.out.println("resources");
         else 
            System.out.println("uri: "+uri);
            this.salvaVisita(pRequest);

            if (uri.equals("/") || uri.equals("/cheapig/")) 
                pRequest.getSession().setAttribute("loadCidadeList", true);
             else 
                pRequest.getSession().setAttribute("loadCidadeList", false);
            
            String vUrl = uri.substring("/cheapig".length());
            String vLogado = (String) vSession.getAttribute(ConstantesSessao.SESSION_LOGADO);

            if (vLogado == null) 
                vLogado = ConstantesGenericas.SIGLA_NAO;
                vSession.setAttribute(ConstantesSessao.SESSION_LOGADO, ConstantesGenericas.SIGLA_NAO);
            

            if (vLogado.equals(ConstantesGenericas.SIGLA_SIM) && vUrl.contains("login")) 
                String vRedirectURL = "/cheapig/";  
                response.sendRedirect(vRedirectURL);
            else
                Usuario vUsuario = (Usuario) vSession.getAttribute(ConstantesSessao.SESSION_USUARIO);

                try 
                    if (vLogado.equalsIgnoreCase(ConstantesGenericas.SIGLA_NAO)) 
                        this.controleAcesso.verificaPermissoesAnonimas(vUrl);
                     else 
                        this.controleAcesso.verificaAcesso(vUsuario, vUrl);
                                       
                    pRequest.getRequestDispatcher(uri).forward(pRequest, response);
                 catch (ControleAcessoException e) 
                    Request vRequest = new Request();
                    vRequest.setRequestURI(uri);
                    vRequest.setRequestURL(vUrl);
                    vRequest.setRequestParameters(WorkflowUtil.montaParametrosURLParameterMap(pRequest.getParameterMap()));
                    vRequest.setRequestMethod(pRequest.getMethod());
                    vSession.setAttribute("COMING_REQUEST", vRequest);
                    response.sendRedirect("/cheapig/loginTela");
                    return;
                   
            


        
    

    public ControleAcesso getControleAcesso() 
        return this.controleAcesso;
    

    public LocaleResolver getLocaleResolver() 
        return this.localeResolver;
    

    @Override
    protected void initFilterBean() throws ServletException 

    

    /**
     * Coleta as informações do usuário e persiste no histórico de visitas
     * 
     * @param request
     *            HttpServletRequest
     */
    private void salvaVisita(HttpServletRequest request) 
        String locale = request.getLocale().getLanguage();
        String browser = CheapigUtil.identificaBrowser(request.getHeader(ConstantesHistoricoVisitas.HTTP_BROWSER));
        String metodo = request.getMethod();
        String ip = request.getRemoteAddr();
        String pagAcessada = request.getRequestURI();
        String host = request.getRemoteHost();

        HistoricoVisitas historico = new HistoricoVisitas();
        historico.setLinguagemNavegador(locale);
        historico.setBrowser(browser);
        historico.setDataAcesso(new Date());
        historico.setHost(host);
        historico.setIpVisitante(ip);
        historico.setPagAcessada(pagAcessada);
        historico.setRequestMethod(metodo);

        this.historicoVisitasController.cadastrarHistoricoVisitas(historico);
    

    public void setControleAcesso(ControleAcesso controleAcesso) 
        this.controleAcesso = controleAcesso;
    

    public void setLocaleResolver(LocaleResolver localeResolver) 
        this.localeResolver = localeResolver;
    

任何人都可以帮助我或给我一个提示?

提前致谢! :D

【问题讨论】:

您找到此问题的根本原因了吗?你能分享解决方案吗?谢谢 不,我还没有找到解决方案。 【参考方案1】:

这个问题的唯一逻辑原因是有多个请求,因为您的过滤器正在实现OncePerRequestFilter

可能的原因是重定向和事件冒泡(例如,单击一个元素,该元素后面有其他元素也具有 onclick 功能)。

【讨论】:

【参考方案2】:

为什么你认为过滤器被调用了两次?您是否在调试器上有断点或在日志中看到它?

当我最近遇到类似问题时(过滤器中的调试器断点命中两次),我发现我的浏览器每次点击发出 2 个请求 - 一个用于实际页面,另一个用于 favicon.ico 文件(书签中显示的小图标/favorites 或在 google chrome 的 url 栏中)。

【讨论】:

【参考方案3】:

重定向可能有问题?

根据您的doFilterInternal 实施,您在特定条件下进行重定向。重定向会创建一个新请求,因此会第二次调用过滤器。

【讨论】:

【参考方案4】:

我的问题是我的过滤器类上有一个@WebFilter 注释。

【讨论】:

以上是关于DelegatingFilterProxy 在没有 Spring Security 的 Spring MVC 应用程序上调用了两次的主要内容,如果未能解决你的问题,请参考以下文章

DelegatingFilterProxy 的独立 Spring 上下文

DelegatingFilterProxy类的作用

DelegatingFilterProxy 中的 NoSuchBeanDefinitionException

spring DelegatingFilterProxy 过滤器 的原理及运用

Spring Security 梳理 - DelegatingFilterProxy

DelegatingFilterProxy(委派过滤器代理类)使用