JEECMS——过滤器和拦截器

写在前面

如果图片显示不清楚的,可以右键图片,选择在新标签页中打开图片

注:过滤器和拦截器是项目的关键部分,因为每一次请求都会经过不同的过滤器和拦截器,进行一系列的包装,判断,过滤等等,只有先了解了这个部分,知道了请求过来时JEECMS都事先做了哪些事,然后再去深入项目中的功能实现,会更加快速便捷。

一.概况

JEECMS配置的过滤器

过滤器 映射关系
ProcessTimeFilter *.do *.jspx *.jhtml *.htm *.jsp
CharacterEncodingFilter *.do *.jspx *.jhtml *.htm *.jsp
OpenSessionInViewFilter *.do *.jspx *.jhtml *.htm *.jsp /
DelegatingFilterProxy /*
XssFilter *.jspx *.jhtml *.html *.jsp
ResourceFilter /wenku/*

注:其配置位置在web.xml中,如果对项目入口配置文件还不太熟悉,可以先看看 JAVA开源CMS内容管理系统JEECMS—-web.xml配置

JEECMS配置的主要的拦截器

拦截器 类别
AdminContextInterceptor 后台拦截器
AdminLocaleInterceptor 后台拦截器
FireWallInterceptor 后台拦截器
FrontContextInterceptor 前台拦截器
FrontLocaleInterceptor 前台拦截器

前台拦截器配置(jeecms-servlet-front.xml中)

<!-- 这个其实已经过时了,不知道为什么还用 -->
    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="frontContextInterceptor"/>
                <ref bean="frontLocaleInterceptor"/>
            </list>
        </property>
    </bean>
    <bean id="frontContextInterceptor" class="com.jeecms.cms.web.FrontContextInterceptor"/>
    <bean id="frontLocaleInterceptor" class="com.jeecms.cms.web.FrontLocaleInterceptor"/>

后台拦截器配置(jeecms-servlet-admin.xml中)

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="adminContextInterceptor"/>
                <ref bean="adminLocaleIntercept"/>
                <ref bean="fireWallInterceptor"/>
            </list>
        </property>
    </bean>
    <bean id="adminContextInterceptor" class="com.jeecms.cms.web.AdminContextInterceptor">
        <!--<property name="adminId" value="1"/>-->
        <property name="auth" value="true"/>
        <property name="excludeUrls">
            <list>
                <value>/login.do</value>
                <value>/logout.do</value>
            </list>
        </property>
    </bean>
    <bean id="adminLocaleIntercept" class="com.jeecms.cms.web.AdminLocaleInterceptor"/>
    <bean id="fireWallInterceptor" class="com.jeecms.cms.web.FireWallInterceptor"/>

注:如果对目录结构还不太熟悉,可以先看看 JAVA开源CMS内容管理系统JEECMS—-项目包结构

二.过滤器细化

ProcessTimeFilter

这个是JEECMS自定义的一个Filter,计算一个请求执行的时间

package com.jeecms.common.web;

import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.*;

/**
 * 执行时间过滤器
 * 请求的映射:*.do *.jspx *.jhtml *.htm *.jsp
 * LiuChengxiang
 * @time 2017年11月21日下午2:52:35
 *
 */
public class ProcessTimeFilter implements Filter {

    protected final Logger log = LoggerFactory.getLogger(ProcessTimeFilter.class);

    /**
     * 请求执行开始时间
     */
    public static final String START_TIME = "_start_time";

    /**
     * 作用:仅仅是计算程序处理一个请求所花费的时间(如果没有这方面的需求,这个过程可以剔除以优化程序响应速度)
     */
    public void doFilter(ServletRequest req, ServletResponse response,FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        long time = System.currentTimeMillis();
        request.setAttribute(START_TIME, time);
        chain.doFilter(request, response);
        time = System.currentTimeMillis() - time;
        log.debug("process in {} ms: {}", time, request.getRequestURI());
    }

    public void init(FilterConfig arg0) throws ServletException {}
    public void destroy() {}
}

CharacterEncodingFilter

Spring-web自带的编码过滤器。调用过程为FilterChain—>OncePerRequestFilter.doFilter()—>CharacterEncodingFilter.doFilterInternal()

@Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String encoding = getEncoding();
        //此处设置请求和响应的编码
        if (encoding != null) {
            if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
                request.setCharacterEncoding(encoding);
            }
            if (isForceResponseEncoding()) {
                response.setCharacterEncoding(encoding);
            }
        }
        filterChain.doFilter(request, response);
    }

OpenSessionInViewFilter

Spring-orm自带的过滤器,它将Hibernate会话绑定到线程,以完成请求的整个处理过程。用于“开放会话视图”模式,即允许在web视图中延迟加载,尽管最初的事务已经完成。原理:让SessionHolder持有session对象,再以sessionFactory为键,sessionHolder为值,放入TransactionSynchronizationManager的类型为ThreadLocal(线程对象的成员变量)的成员变量中,这样就实现了线程绑定,一个请求一个线程,一个请求一个会话session

@Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        SessionFactory sessionFactory = lookupSessionFactory(request);
        boolean participate = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        String key = getAlreadyFilteredAttributeName();

        if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
            // Do not modify the Session: just set the participate flag.
            participate = true;
        }
        else {
            boolean isFirstRequest = !isAsyncDispatch(request);
            if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) {
                logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
                Session session = openSession(sessionFactory);
                /**
                让SessionHolder持有session对象,再以sessionFactory为键,sessionHolder为值,放入
                TransactionSynchronizationManager的类型为ThreadLocal(线程对象的成员变量)的成员
                变量中,这样就实现了线程绑定,一个请求一个线程,一个请求一个会话session
                */
                SessionHolder sessionHolder = new SessionHolder(session);
                TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);

                AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder);
                asyncManager.registerCallableInterceptor(key, interceptor);
                asyncManager.registerDeferredResultInterceptor(key, interceptor);
            }
        }

        try {
            filterChain.doFilter(request, response);
        }

        finally {
            if (!participate) {
                SessionHolder sessionHolder =
                        (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
                if (!isAsyncStarted(request)) {
                    logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
                    SessionFactoryUtils.closeSession(sessionHolder.getSession());
                }
            }
        }
    }

DelegatingFilterProxy

Spring的过滤器的委派代理类,本质是一种责任链模式的链式调用

这里写图片描述

XssFilter

防止XSS攻击,将请求包装成XssHttpServletRequestWrapper

package com.jeecms.common.web;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import com.jeecms.core.web.util.URLHelper;
/**
 * 请求包装过滤器(作用:防止脚本攻击)
 * 映射的请求:*.jspx *.jhtml *.html *.jsp
 * LiuChengxiang
 * @time 2017年11月13日上午10:57:51
 *
 */
public class XssFilter implements Filter {

    private String excludeUrls;
    FilterConfig filterConfig = null;

    public void init(FilterConfig filterConfig) throws ServletException {
        this.excludeUrls=filterConfig.getInitParameter("excludeUrls");
        this.filterConfig = filterConfig;
    }

    public void destroy() {
        this.filterConfig = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {
        if(isExcludeUrl(request)){
            chain.doFilter(request, response);
        }else{
            //除了/member,/flow_statistic,/search,/api开头的请求,其余请求都需要进行请求包装
            chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
        }
    }

    private boolean isExcludeUrl(ServletRequest request){
        boolean exclude=false;
        if(StringUtils.isNotBlank(excludeUrls)){
             String[] excludeUrl = excludeUrls.split("@");
             if(excludeUrl!=null&&excludeUrl.length>0){
                 for(String url:excludeUrl){
                     if(URLHelper.getURI((HttpServletRequest)request).startsWith(url)){
                         exclude=true;
                     }
                 }
             }
        }
        return exclude;
    }

}

我们来看一下JEECMS这个防攻击是怎么做的

这里写图片描述

ResourceFilter

指定资源访问过滤器

package com.jeecms.common.web;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jeecms.common.util.StrUtils;

/**
 * 资源过滤器
 * 映射的路径:/wenku/*
 * LiuChengxiang
 * @time 2017年11月21日下午2:54:48
 *
 */
public class ResourceFilter implements Filter {
    protected final Logger log = LoggerFactory.getLogger(ResourceFilter.class);

    public void destroy() {}

    @SuppressWarnings("static-access")
    public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        String uri=request.getRequestURI();
        String suffix=StrUtils.getSuffix(uri);
        //wenku目录下只运行直接访问的资源只有swf和pdf
        //swf为v8之前版本的格式 pdf为v8的版本格式
        if(!suffix.equals("swf")&&!suffix.equals("pdf")){
            response.sendError(response.SC_FORBIDDEN);
        }else{
            chain.doFilter(request, response);
        }
    }

    public void init(FilterConfig arg0) throws ServletException {}
}

三.拦截器细化

AdminContextInterceptor

CMS后台上下文信息拦截器,主要做了两件事:1.获取当前用户放入当前请求和当前线程 2.获取访问的站点放入当前线程

<bean id="adminContextInterceptor" class="com.jeecms.cms.web.AdminContextInterceptor">
        <!--<property name="adminId" value="1"/>-->
        <property name="auth" value="true"/>
        <property name="excludeUrls">
            <list>
                <!-- 不拦截登录,登出 -->
                <value>/login.do</value>
                <value>/logout.do</value>
            </list>
        </property>
    </bean>
@Override
    public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {
        // 获得用户
        CmsUser user = null;
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            String username =  (String) subject.getPrincipal();
            user = cmsUserMng.findByUsername(username);
        }
        // 将用户信息放入当前请求
        CmsUtils.setUser(request, user);
        // 将用户信息与当前线程绑定
        CmsThreadVariable.setUser(user);
        // 获得站点
        CmsSite oldSite=getByCookie(request);
        CmsSite site = getSite(user,request, response);
        CmsUtils.setSite(request, site);
        // 将站点信息与当前线程绑定
        CmsThreadVariable.setSite(site);
        String uri = getURI(request);
        if (exclude(uri)) {
            return true;
        }
        //切换站点移除shiro缓存
        if(oldSite!=null&&!oldSite.equals(site)&&user!=null){
            authorizingRealm.removeUserAuthorizationInfoCache(user.getUsername().toString());
        }
        //没有该站管理权限(则切换站点?)
        if(site!=null&&user!=null&&user.getUserSite(site.getId())==null){
            Set<CmsUserSite>userSites=user.getUserSites();
            if(userSites!=null&&userSites.size()>0){
                 CmsSite s= userSites.iterator().next().getSite();
                 authorizingRealm.removeUserAuthorizationInfoCache(user.getUsername().toString());
                 CmsUtils.setSite(request, s);
                 CmsThreadVariable.setSite(s);
                 response.sendRedirect(s.getAdminUrl());
            }
        }
        return true;
    }

AdminLocaleInterceptor

后台本地化信息拦截器

这里写图片描述

FireWallInterceptor

网站防火墙拦截器
这里写图片描述

FrontContextInterceptor

CMS前台上下文信息拦截器

package com.jeecms.cms.web;

import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.jeecms.common.util.CheckMobile;
import com.jeecms.common.web.CookieUtils;
import com.jeecms.common.web.session.SessionProvider;
import com.jeecms.core.entity.*;
import com.jeecms.core.manager.*;
import com.jeecms.core.web.util.CmsUtils;

public class FrontContextInterceptor extends HandlerInterceptorAdapter {

    public static final String SITE_COOKIE = "_site_id_cookie";

    /**
     * 1.获取所有站点==>2.获取当前站点==>为当前站点设置cookie==>将当前请求的站点信息存入request中
     *   ==>将当前请求的站点信息存入当前线程对象中==>当前登录的用户信息存入request中==>
     *   ==>当前登录的用户信息存入当前线程对象中==>获取请求来源(pc/phone)
     * @author LiuChengxiang
     * @time 2017年11月21日下午4:39:35
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws ServletException
     */
    @Override
    public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler)throws ServletException {
        CmsSite site = null;
        //获取数据库中的所有站点
        List<CmsSite> list = cmsSiteMng.getListFromCache();
        int size = list.size();
        if (size == 0) {
            throw new RuntimeException("no site record in database!");
        } else if (size == 1) {
            site = list.get(0);
        } else {
            String server = request.getServerName();
            String alias, redirect;
            for (CmsSite s : list) {
                // 检查域名
                if (s.getDomain().equals(server)) {
                    site = s;
                    break;
                }
                // 检查域名别名
                alias = s.getDomainAlias();
                if (!StringUtils.isBlank(alias)) {
                    for (String a : StringUtils.split(alias, ',')) {
                        if (a.equals(server)) {
                            site = s;
                            break;
                        }
                    }
                }
                // 检查重定向
                redirect = s.getDomainRedirect();
                if (!StringUtils.isBlank(redirect)) {
                    for (String r : StringUtils.split(redirect, ',')) {
                        if (r.equals(server)) {
                            try {
                                response.sendRedirect(s.getUrl());
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                            return false;
                        }
                    }
                }
            }
            if (site == null) {
                throw new SiteNotFoundException(server);
            }
        }
        if(site!=null){
            //设置对应站点的cookie
            CookieUtils.addCookie(request, response, SITE_COOKIE, site.getId().toString(), null, null);
        }
        //将当前请求的站点信息存入request中
        CmsUtils.setSite(request, site);
        //将当前请求的站点信息存入当前线程对象的局部变量ThreadLocal.ThreadLocalMap中
        CmsThreadVariable.setSite(site);
        Subject subject = SecurityUtils.getSubject();
        CmsUser user =null;
        if (subject.isAuthenticated()|| subject.isRemembered()) {
            String username =  (String) subject.getPrincipal();
            user= cmsUserMng.findByUsername(username);
            CmsUtils.setUser(request, user);
            // Site加入线程变量
            CmsThreadVariable.setUser(user);
        }
        checkEquipment(request, response);
        return true;
    }

    /**
     * 检查访问方式
     */
    public void checkEquipment(HttpServletRequest request,HttpServletResponse response){
        String ua=(String) session.getAttribute(request,"ua");
        if(null==ua){
            try{
                String userAgent = request.getHeader( "USER-AGENT" ).toLowerCase();
                if(null == userAgent){  
                    userAgent = "";  
                }
                if(CheckMobile.check(userAgent)){
                    ua="mobile";
                } else {
                    ua="pc";
                }
                session.setAttribute(request, response, "ua",ua);
            }catch(Exception e){}
        }
        if(StringUtils.isNotBlank((ua) )){
            request.setAttribute("ua", ua);
        }
    }
}

FrontLocaleInterceptor

前台本地化信息拦截器(与后台的处理方式一致,可参考AdminLocaleInterceptor )

四.话外音

如有错误的地方,欢迎在文章下方留言批评,博主会第一时间回复。欢迎大家与博主交流,共同进步。

系列章节链接直达

JEECMS——前言
JEECMS——源码下载及安转运行
JEECMS——项目包结构
JEECMS——web.xml配置
JEECMS——安全框架Shiro

猜你喜欢

转载自blog.csdn.net/weixin_37490221/article/details/78667479