(1)登录页面:
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
|
<%@page import="java.security.SecureRandom"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<
html
>
<
head
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=UTF-8"
>
<
base
href="<%=basePath%>">
<
title
>SpringMVC Cookie Demo</
title
>
<%
SecureRandom random = new SecureRandom();
random.setSeed(8738);
double _csrf = random.nextDouble();
session.setAttribute("_csrf", _csrf);
%>
</
head
>
<
body
>
<
div
align
=
"center"
>
<
h2
>SpringMVC Cookie Demo</
h2
>
<
form
action
=
"check.html"
method
=
"post"
>
<
table
>
<
tr
>
<
td
>用户名:</
td
>
<
td
><
input
type
=
"text"
name
=
"username"
/></
td
>
</
tr
>
<
tr
>
<
td
>密码:</
td
>
<
td
><
input
type
=
"password"
name
=
"password"
/></
td
>
</
tr
>
<
tr
>
<
td
><
input
name
=
"remember-me"
type
=
"checkbox"
>30天内自动登录</
input
></
td
>
</
tr
>
<
tr
>
<
td
colspan
=
"2"
align
=
"center"
><
input
type
=
"submit"
value
=
"登录"
/>
<
input
type
=
"reset"
value
=
"重置"
/></
td
>
</
tr
>
</
table
>
<
input
type
=
"hidden"
name
=
"_csrf"
value="<%=_csrf %>" />
</
form
>
</
div
>
</
body
>
</
html
>
|
从上面的代码可知,为了防止CSRF攻击,因此在form表单里添加了一个隐藏字段“_csrf”,其值是生成的一个随机小数
(2)在SpringMVC的配置文件中添加拦截器:
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
|
<?
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:context
=
"http://www.springframework.org/schema/context"
xmlns:cache
=
"http://www.springframework.org/schema/cache"
xmlns:mvc
=
"http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"
default-lazy-init
=
"true"
>
<
mvc:annotation-driven
/>
<!-- 组件扫描 -->
<
context:component-scan
base-package
=
"cn.zifangsky.controller"
/>
<
context:component-scan
base-package
=
"cn.zifangsky.manager.impl"
/>
<!-- 配置直接转发的页面 -->
<
mvc:view-controller
path
=
"/login.html"
view-name
=
"login"
/>
<
mvc:view-controller
path
=
"/user/callback.html"
view-name
=
"user/callback"
/>
<!-- 拦截器 -->
<
mvc:interceptors
>
<
mvc:interceptor
>
<!-- 对登录操作进行拦截 -->
<
mvc:mapping
path
=
"/check.html"
/>
<
bean
class
=
"cn.zifangsky.interceptor.LoginInterceptor"
/>
</
mvc:interceptor
>
<
mvc:interceptor
>
<!-- 对/user/**的请求进行拦截 -->
<
mvc:mapping
path
=
"/user/**"
/>
<
bean
class
=
"cn.zifangsky.interceptor.UserInterceptor"
/>
</
mvc:interceptor
>
</
mvc:interceptors
>
<!-- 视图解析 -->
<
bean
class
=
"org.springframework.web.servlet.view.InternalResourceViewResolver"
>
<
property
name
=
"prefix"
value
=
"/WEB-INF/pages/"
/>
<
property
name
=
"suffix"
value
=
".jsp"
/>
</
bean
>
</
beans
>
|
从上面的代码知道,在这个文件中添加了一个 mvc:interceptors 标签,表示一系列的拦截器集合,然后下面定义了对登录时form表单提交地址“/check.html”进行拦截。下面一行的bean属性就是定义了自定义拦截器的类所在的路径
注:后面那个拦截器这里不用管,我在写后面的文章时才会用到
(3)自定义拦截器LoginInterceptor:
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
|
package
cn.zifangsky.interceptor;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
javax.servlet.http.HttpSession;
import
org.apache.commons.lang3.StringUtils;
import
org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public
class
LoginInterceptor
extends
HandlerInterceptorAdapter {
/**
* 用于在登录前验证 _csrf 参数
* */
public
boolean
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws
Exception {
HttpSession session = request.getSession();
String _csrfByForm = request.getParameter(
"_csrf"
);
//表单中的值
String _csrfBySession = String.valueOf(session.getAttribute(
"_csrf"
));
//session中的值
session.removeAttribute(
"_csrf"
);
//使用之后从session中删掉
//验证是否存在CSRF攻击
if
(StringUtils.isNotBlank(_csrfByForm) && StringUtils.isNotBlank(_csrfBySession) && _csrfByForm.equals(_csrfBySession)){
return
true
;
}
else
{
response.setContentType(
"text/html;charset=utf-8"
);
response.setStatus(
403
);
//页面友好提示信息
OutputStream oStream = response.getOutputStream();
oStream.write(
"请不要重复提交请求,返回原始页面刷新后再次尝试!!!"
.getBytes(
"UTF-8"
));
return
false
;
}
}
public
void
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws
Exception {
super
.afterCompletion(request, response, handler, ex);
}
}
|
这个自定义拦截器的逻辑很简单,就是把form表单隐藏域“_csrf”中的值和session中的“_csrf”值进行比较。如果二者相同,则说明该请求是从前台form表单中传进来的,而不是其他网站的伪造请求(PS:因为这种方式没法向session中定义“_csrf”参数);同时也防止form表单的重复提交(PS:因为第一次验证过后session中的“_csrf”就已经被移除了,除非前台刷新页面才会重新生成),避免了爆破撞库等安全隐患。当然,为了进一步降低安全隐患,这里的form表单还应该添加复杂的动态验证码。我这里是由于为了让示例更简洁,因此就把这一步给省略了
(4)验证:
第一次提交表单,发现可以正常到达后台进行验证
第二次点击浏览器的“返回键”,返回到表单页面之后重复提交,可以发现直接被拦截了。效果如下:
本文转自 pangfc 51CTO博客,原文链接:http://blog.51cto.com/983836259/1877586,如需转载请自行联系原作者