高手的存在,就是让服务10亿人的时候,你感觉只是为你一个人服务......

iBase4J 源码之Interceptor拦截器

目录
  1. 1. 第一步:创建自定义拦截器类
    1. 1.1. BaseInterceptor
    2. 1.2. 国际化信息设置(基于SESSION)-LocaleInterceptor
    3. 1.3. 日志拦截器-EventInterceptor
    4. 1.4. 恶意请求拦截器-MaliciousRequestInterceptor
  2. 2. 第二步:注册interceptor

Interceptor拦截器作为AOP的一种实现,经常用于日志记录、权限验证、恶意请求、数据稽核、性能监控等。


提到Interceptor,我总会想到Filter,两者还是有很大区别的。当然springmvc 在处理请求的时候经常会集成两者。

关于Filter与Interceptor的请求处理流程,参见下图
Alt text

在iBase4J-SYS中自定义了一个BaseInterceptor拦截器基类和三个子拦截器,分别处理

  1. 国际化信息设置(基于SESSION)
  2. 日志拦截
  3. 恶意请求拦截

实现自定义拦截器只需要两步

第一步:创建自定义拦截器类

Spring为我们提供了org.springframework.web.servlet.handler.HandlerInterceptorAdapter这个适配器,继承此类,可以非常方便的实现自己的拦截器。

BaseInterceptor

iBase4J-SYS创建BaseInterceptor继承了HandlerInterceptorAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 拦截器基类
*
*/

public class BaseInterceptor extends HandlerInterceptorAdapter {
protected final Logger logger = LogManager.getLogger();
private BaseInterceptor[] nextInterceptor;

public void setNextInterceptor(BaseInterceptor... nextInterceptor) {
this.nextInterceptor = nextInterceptor;
}

// 在请求处理之前进行调用(Controller方法调用之前)
// 方法的返回值决定逻辑是否继续执行, true,表示继续执行, false, 表示不再继续执行。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {

if (nextInterceptor == null) {
return true;
}
for (int i = 0; i < nextInterceptor.length; i++) {
if (!nextInterceptor[i].preHandle(request, response, handler)) {
return false;
}
}
return true;
}

// 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception
{

if (nextInterceptor != null) {
for (int i = 0; i < nextInterceptor.length; i++) {
nextInterceptor[i].postHandle(request, response, handler, modelAndView);
}
}
}

// 在整个请求结束之后被调用,也就是在DispatcherServlet,渲染了对应的视图之后执行(主要是用于进行资源清理工作)
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {

if (nextInterceptor != null) {
for (int i = 0; i < nextInterceptor.length; i++) {
nextInterceptor[i].afterCompletion(request, response, handler, ex);
}
}
}

public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {

if (nextInterceptor != null) {
for (int i = 0; i < nextInterceptor.length; i++) {
nextInterceptor[i].afterConcurrentHandlingStarted(request, response, handler);
}
}
}
}

继而自定义了三个拦截器继承BaseInterceptor

国际化信息设置(基于SESSION)-LocaleInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* 国际化信息设置(基于SESSION)
*
* @author ShenHuaJie
* @version 2016年5月20日 下午3:16:45
*/

public class LocaleInterceptor extends BaseInterceptor {
protected static Logger logger = LogManager.getLogger();

//解析http的user-agent信息
static UASparser uasParser;

static {
try {
uasParser = new UASparser(OnlineUpdater.getVendoredInputStream());
} catch (IOException e) {
logger.error("", e);
}
}

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HttpSession session = request.getSession();
// 设置客户端语言
Locale locale = (Locale) session.getAttribute("LOCALE");
if (locale == null) {
String language = request.getParameter("locale");
if (StringUtils.isNotBlank(language)) {
locale = new Locale(language);
session.setAttribute("LOCALE", locale);
} else {
locale = request.getLocale();
}
}
LocaleContextHolder.setLocale(locale);
// 客户端IP
String clientIp = (String) session.getAttribute(Constants.USER_IP);
if (clientIp == null) {
session.setAttribute(Constants.USER_IP, WebUtil.getHost(request));
}
// 客户端代理
String userAgent = (String) session.getAttribute(Constants.USER_AGENT);
if (userAgent == null) {
try {
UserAgentInfo userAgentInfo = uasParser.parse(request.getHeader("user-agent"));
userAgent = userAgentInfo.getOsName() + " " + userAgentInfo.getType() + " " + userAgentInfo.getUaName();
String uuid = request.getHeader("UUID");
if ("unknown unknown unknown".equals(userAgent) && StringUtils.isNotBlank(uuid)) {
userAgent = uuid;
}
session.setAttribute(Constants.USER_AGENT, userAgent);
} catch (IOException e) {
logger.error("", e);
}
}
return super.preHandle(request, response, handler);
}
}

日志拦截器-EventInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* 日志拦截器
*
* @author ShenHuaJie
* @version 2016年6月14日 下午6:18:46
*/

public class EventInterceptor extends BaseInterceptor {
protected static Logger logger = LogManager.getLogger();

private final ThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("ThreadLocal StartTime");
private ExecutorService executorService = Executors.newCachedThreadPool();

@Autowired
@Qualifier("sysProvider")
protected BaseProvider sysProvider;

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 开始时间(该数据只有当前请求的线程可见)
startTimeThreadLocal.set(System.currentTimeMillis());
return super.preHandle(request, response, handler);
}

public void afterCompletion(final HttpServletRequest request, HttpServletResponse response, Object handler,
final Exception ex) throws Exception {

final Long startTime = startTimeThreadLocal.get();
final Long endTime = System.currentTimeMillis();
String path = request.getServletPath();
// 保存日志
if (handler instanceof HandlerMethod) {
try {
Object uid = WebUtil.getCurrentUser(request);
String userAgent = (String) request.getSession().getAttribute(Constants.USER_AGENT);
String clientIp = (String) request.getSession().getAttribute(Constants.USER_IP);
if (!path.contains("/read/") && !path.contains("/get") && !path.contains("/unauthorized")
&& !path.contains("/forbidden")) {

final SysEvent record = new SysEvent();
record.setMethod(request.getMethod());
record.setRequestUri(path);
record.setClientHost(clientIp);
record.setUserAgent(userAgent);
if (path.contains("/upload")) {
record.setParameters("");
} else {
String param = JSON.toJSONString(WebUtil.getParameterMap(request));
record.setParameters(param.length() > 1024 ? param.substring(0, 1024) : param);
}
record.setStatus(response.getStatus());
if (uid != null) {
record.setCreateBy(Long.parseLong(uid.toString()));
record.setUpdateBy(Long.parseLong(uid.toString()));
}
final String msg = (String) request.getAttribute("msg");
try {
HandlerMethod handlerMethod = (HandlerMethod) handler;
ApiOperation apiOperation = handlerMethod.getMethod().getAnnotation(ApiOperation.class);
record.setTitle(apiOperation.value());
} catch (Exception e) {
logger.error("", e);
}
executorService.submit(new Runnable() {
public void run() {
try { // 保存操作
if (StringUtils.isNotBlank(msg)) {
record.setRemark(msg);
} else {
record.setRemark(ExceptionUtil.getStackTraceAsString(ex));
}

Parameter parameter = new Parameter("sysEventService", "update", record);
sysProvider.execute(parameter);
} catch (Exception e) {
logger.error("Save event log cause error :", e);
}
}
});

} else if (path.contains("/unauthorized")) {
logger.warn("用户[{}]没有登录", clientIp + "@" + userAgent);
} else if (path.contains("/forbidden")) {
logger.warn("用户[{}]没有权限", WebUtil.getCurrentUser() + "@" + clientIp + "@" + userAgent);
} else {
logger.info(uid + "@" + path + "@" + clientIp + userAgent);
}
} catch (Exception e) {
logger.error("", e);
}
}
// 内存信息
if (logger.isDebugEnabled()) {
String message = "开始时间: {}; 结束时间: {}; 耗时: {}s; URI: {}; ";
// 最大内存: {}M; 已分配内存: {}M; 已分配内存中的剩余空间: {}M;
// 最大可用内存:
// {}M.
// long total =
// Runtime.getRuntime().totalMemory() /
// 1024 / 1024;
// long max =
// Runtime.getRuntime().maxMemory() /
// 1024 / 1024;
// long free =
// Runtime.getRuntime().freeMemory()
// /
// 1024 / 1024;
// , max, total, free, max - total + free
logger.debug(message, DateUtil.format(startTime, "HH:mm:ss.SSS"), DateUtil.format(endTime, "HH:mm:ss.SSS"),
(endTime - startTime) / 1000.00, path);

}
super.afterCompletion(request, response, handler, ex);
}
}

恶意请求拦截器-MaliciousRequestInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 恶意请求拦截器
*
* @author ShenHuaJie
* @version 2016年5月20日 下午3:16:57
*/

public class MaliciousRequestInterceptor extends BaseInterceptor {
private Boolean allRequest = false; // 拦截所有请求,否则拦截相同请求
private Long minRequestIntervalTime; // 允许的最小请求间隔
private Integer maxMaliciousTimes; // 允许的最大恶意请求次数

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {

response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST,GET,PUT,OPTIONS,DELETE");
response.setHeader("Access-Control-Allow-Headers",
"x-requested-with,Access-Control-Allow-Origin,EX-SysAuthToken,EX-JSESSIONID");

String url = request.getServletPath();
if (url.endsWith("/unauthorized") || url.endsWith("/forbidden")) {
return super.preHandle(request, response, handler);
}
HttpSession session = request.getSession();
String preRequest = (String) session.getAttribute(Constants.PREREQUEST);
Long preRequestTime = (Long) session.getAttribute(Constants.PREREQUEST_TIME);
if (preRequestTime != null && preRequest != null) { // 过滤频繁操作
if ((url.equals(preRequest) || allRequest)
&& System.currentTimeMillis() - preRequestTime < minRequestIntervalTime) {
Integer maliciousRequestTimes = (Integer) session.getAttribute(Constants.MALICIOUS_REQUEST_TIMES);
if (maliciousRequestTimes == null) {
maliciousRequestTimes = 1;
} else {
maliciousRequestTimes++;
}
session.setAttribute(Constants.MALICIOUS_REQUEST_TIMES, maliciousRequestTimes);
if (maliciousRequestTimes > maxMaliciousTimes) {
response.setStatus(HttpCode.MULTI_STATUS.value());
logger.warn("To intercept a malicious request : {}", url);
return false;
}
} else {
session.setAttribute(Constants.MALICIOUS_REQUEST_TIMES, 0);
}
}
session.setAttribute(Constants.PREREQUEST, url);
session.setAttribute(Constants.PREREQUEST_TIME, System.currentTimeMillis());
return super.preHandle(request, response, handler);
}

public void setAllRequest(Boolean allRequest) {
this.allRequest = allRequest;
}

public void setMinRequestIntervalTime(Long minRequestIntervalTime) {
this.minRequestIntervalTime = minRequestIntervalTime;
}

public void setMaxMaliciousTimes(Integer maxMaliciousTimes) {
this.maxMaliciousTimes = maxMaliciousTimes;
}
}

第二步:注册interceptor

iBase4J-SYS 关于interceptor的注册在Spring-servlet.xml中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/*.ico" />
<mvc:exclude-mapping path="/*/api-docs" />
<mvc:exclude-mapping path="/swagger**" />
<mvc:exclude-mapping path="/webjars/**" />
<mvc:exclude-mapping path="/configuration/**" />
<bean class="org.ibase4j.core.interceptor.LocaleInterceptor">
<property name="nextInterceptor">
<array>
<bean class="org.ibase4j.core.interceptor.EventInterceptor" />
<bean class="org.ibase4j.core.interceptor.MaliciousRequestInterceptor">
<property name="minRequestIntervalTime" value="500" />
<property name="maxMaliciousTimes" value="0" />
</bean>
</array>
</property>
</bean>
</mvc:interceptor>
</mvc:interceptors>