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
|
package
com.uncle5.pubrub.web.common;
import
java.util.UUID;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpSession;
public
final
class
CsrfTokenManager {
// 隐藏域参数名称
static
final
String CSRF_PARAM_NAME =
"CSRFToken"
;
// session中csrfToken参数名称
public
static
final
String CSRF_TOKEN_FOR_SESSION_ATTR_NAME = CsrfTokenManager.
class
.getName() +
".tokenval"
;
private
CsrfTokenManager() {
};
// 在session中创建csrfToken
public
static
String createTokenForSession(HttpSession session) {
String token =
null
;
synchronized
(session) {
token = (String) session
.getAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME);
if
(
null
== token) {
token = UUID.randomUUID().toString();
session.setAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME, token);
}
}
return
token;
}
public
static
String getTokenFromRequest(HttpServletRequest request) {
return
request.getParameter(CSRF_PARAM_NAME);
}
}
|
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
|
package
com.uncle5.pubrub.web.common;
import
java.util.Map;
import
javax.servlet.http.HttpServletRequest;
import
org.springframework.stereotype.Component;
import
org.springframework.web.servlet.support.RequestDataValueProcessor;
import
com.google.common.collect.Maps;
@Component
(
"requestDataValueProcessor"
)
public
class
CsrfRequestDataValueProcessor
implements
RequestDataValueProcessor {
@Override
public
String processAction(HttpServletRequest request, String action) {
// TODO 暂时原样返回action
return
action;
}
@Override
public
String processFormFieldValue(HttpServletRequest request,
String name, String value, String type) {
// TODO 暂时原样返回value
return
value;
}
@Override
public
Map<String, String> getExtraHiddenFields(HttpServletRequest request) {
//此处是当使用spring的taglib标签<form:form>创建表单时候,增加的隐藏域参数
Map<String, String> hiddenFields = Maps.newHashMap();
hiddenFields.put(CsrfTokenManager.CSRF_PARAM_NAME,
CsrfTokenManager.createTokenForSession(request.getSession()));
return
hiddenFields;
}
@Override
public
String processUrl(HttpServletRequest request, String url) {
// TODO 暂时原样返回url
return
url;
}
}
|
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
|
package
com.uncle5.pubrub.web.security;
import
java.net.URLEncoder;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
org.slf4j.Logger;
import
org.slf4j.LoggerFactory;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.util.StringUtils;
import
org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import
com.uncle5.pubrub.web.common.CsrfTokenManager;
import
com.uncle5.pubrub.web.common.WebUser;
public
class
CsrfInterceptor
extends
HandlerInterceptorAdapter {
private
static
final
Logger logger = LoggerFactory
.getLogger(CsrfInterceptor.
class
);
@Autowired
WebUser webUser;
@Override
public
boolean
preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws
Exception {
if
(
"POST"
.equalsIgnoreCase(request.getMethod())) {
String CsrfToken = CsrfTokenManager.getTokenFromRequest(request);
if
(CsrfToken ==
null
|| !CsrfToken.equals(request.getSession().getAttribute(
CsrfTokenManager.CSRF_TOKEN_FOR_SESSION_ATTR_NAME))) {
String reLoginUrl =
"/login?backurl="
+ URLEncoder.encode(getCurrentUrl(request),
"utf-8"
);
response.sendRedirect(reLoginUrl);
return
false
;
}
}
return
true
;
}
private
String getCurrentUrl(HttpServletRequest request) {
String currentUrl = request.getRequestURL().toString();
if
(!StringUtils.isEmpty(request.getQueryString())) {
currentUrl +=
"?"
+ request.getQueryString();
}
return
currentUrl;
}
}
|
1
2
3
4
5
6
7
8
|
<!-- 安全拦截用的 -->
<
mvc:interceptors
>
<
mvc:interceptor
>
<
mvc:mapping
path
=
"/forum/post"
/>
<
mvc:mapping
path
=
"/forum/reply/*"
/>
<
bean
class
=
"com.uncle5.pubrub.web.security.CsrfInterceptor"
/>
</
mvc:interceptor
>
</
mvc:interceptors
>
|
1
2
3
4
5
6
7
8
9
|
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<
div
style
=
"margin: 0 auto; border: 1px solid orange; width: 80%; height: 400px;"
>
<
form:form
action
=
"/forum/post"
method
=
"post"
>
<
span
>标题:</
span
><
input
type
=
"text"
name
=
"title"
id
=
"title"
>
<
textarea
name
=
"content"
id
=
"content"
rows
=
"30"
cols
=
"40"
></
textarea
>
<
input
type
=
"submit"
name
=
"submit"
value
=
"submit"
>
</
form:form
>
</
div
>
|
1
2
3
4
5
6
7
8
|
<
div
style
=
"margin: 0 auto; border: 1px solid orange; width: 80%; height: 400px;"
>
<
form
id
=
"command"
action
=
"/forum/post"
method
=
"post"
>
<
span
>标题:</
span
><
input
type
=
"text"
name
=
"title"
id
=
"title"
>
<
textarea
name
=
"content"
id
=
"content"
rows
=
"30"
cols
=
"40"
></
textarea
>
<
input
type
=
"submit"
name
=
"submit"
value
=
"submit"
>
<
input
type
=
"hidden"
name
=
"CSRFToken"
value
=
"35afec82-da7b-449e-9ae9-b38664b5af63"
/>
</
form
>
</
div
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
1
.只有当使用spring的form标签时候,才会在渲染jsp页面时候触发
org.springframework.web.servlet.tags.form.FormTag 类中的
@Override
public
int
doEndTag()
throws
JspException {
RequestDataValueProcessor processor = getRequestContext().getRequestDataValueProcessor();
ServletRequest request =
this
.pageContext.getRequest();
if
((processor !=
null
) && (request
instanceof
HttpServletRequest)) {
writeHiddenFields(processor.getExtraHiddenFields((HttpServletRequest) request));
}
this
.tagWriter.endTag();
return
EVAL_PAGE;
}
该方法会调用上文中所说的CsrfRequestDataValueProcessor中的创建隐藏域的方法getExtraHiddenFields来创建csrfToken隐藏域。
2
.对相关需要进行防范csrf攻击的post请求地址进行拦截,此处是依赖springMVC中提供的拦截器机制来操作的
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path=
"/forum/post"
/>
<mvc:mapping path=
"/forum/reply/*"
/>
<bean
class
=
"com.uncle5.pubrub.web.security.CsrfInterceptor"
/>
</mvc:interceptor>
</mvc:interceptors>
当用户访问
"/forum/post"
这个请求时候,会直接进入CsrfInterceptor的preHandle方法,此处针对post请求进行了判断,如果从request中获取的csrfToken不存在或者与session中的csrfToken不相匹配,那么可以不进行后续流程,至于返回
404
还是返回到登录页面,就随便作者了。
|