注:过滤器和拦截器是项目的关键部分,因为每一次请求都会经过不同的过滤器和拦截器,进行一系列的包装,判断,过滤等等,只有先了解了这个部分,知道了请求过来时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