csrf 生成java,一种在SpirngMVC3中防御CSRF攻击的实现

关于CSRF是什么东西,请参见我的博文:《浅析CSRF》。本文将给出一种在 SpringMVC3+Velocity 的框架下防御CSRF攻击的解决方案。主要思想参考 EYAL LUPU[1] 的解决方案,但使用Velocity宏来进行Token值的传递,而不是使用spring form标签。

一、方案概要

在服务器端生成私有的会话级token,使用Velocity的宏命令在客户端的Form表单中插入这个token;在表单提交后对,服务器端对token值进行校验,根据结果来区分该次请求是否合法:正确就放行,不正确就就阻断请求。

在这里,我们约定:凡是更新资源的操作,都通过POST向服务器端发送。这也是符合HTTP规范的约定。

二、Token生成

使用一个 CSRFTokenManager 类来进行token生成的工作,代码如下:

CSRFTokenManager.javacode view1

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

54package com.javan.security;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpSession;

/**

* A manager for the CSRF token for a given session. The {@link #getTokenForSession(HttpSession)} should used to obtain

* the token value for the current session (and this should be the only way to obtain the token value).

*/

public final class CSRFTokenManager{

/**

* The token parameter name

*/

static final String CSRF_PARAM_NAME = "xToken";

/**

* The location on the session which stores the token

*/

public final static String CSRF_TOKEN_FOR_SESSION_ATTR_NAME = CSRFTokenManager.class.getName() + ".tokenval";

public static String getTokenForSession(HttpSession session){

String token = null;

// cannot allow more than one token on a session - in the case of two requests trying to

// init the token concurrently

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;

}

/**

* Extracts the token value from the session

*

* @param request

* @return

*/

public static String getTokenFromRequest(HttpServletRequest request){

String token = request.getParameter(CSRF_PARAM_NAME);

if (token == null || "".equals(token)) {

token = request.getHeader(CSRF_PARAM_NAME);

}

return token;

}

private CSRFTokenManager(){

};

}

getTokenForSession方法用于检查HTTP session中是否存在CSRF token:存在则返回;不存在则生成并存储到session中,然后返回。

三、Token依附到Form

由于我的前端使用 Velocity 进行渲染,并且不使用 Spring form标签,所以为了将token依附到Form表单中,我使用 Velocity 的宏指令进行渲染工作。

SpringMVC 中的配置,主要是配置 Velocity Tools:

mvc-context.xmlcode view1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">

utf-8

utf-8

class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">

.vm

Toolbox.xml中定义生成 CSRF token 的 tool:

toolbox.xmlcode view1

2

3

4

5

6

7

csrfTool

application

com.javan.util.CSRFTool

工具类CSRFTool.java:

CSRFTool.xmlcode view1

2

3

4

5

6

7

8

9

10

11package com.javan.util;

import javax.servlet.http.HttpServletRequest;

import com.javan.security.CSRFTokenManager;

public class CSRFTool {

public static String getToken(HttpServletRequest request) {

return CSRFTokenManager.getTokenForSession(request.getSession());

}

}

页面渲染CSRF token的宏命令:

macros-default.vmcode view1

2

3

4

5

6

7

8

9#** CSRFToken

* Generate a input field of type 'hidden' of token to avoid CSRF attack

*#

#macro( CSRFToken $id)

#if(!$id || $id == "")

#set($id="xToken")

#end

#end

在前端的Form表单中添加token值:

1

2

3

4

#CSRFToken()

...

四、验证Token

当请求提交后,在服务器端验证token值,在这里定义一个拦截器CSRFHandlerInterceptor:

CSRFHandlerInterceptor.javacode view1

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

43package com.javan.security;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;

/**

* A Spring MVC HandlerInterceptor which is responsible to enforce CSRF token validity on incoming posts

* requests. The interceptor should be registered with Spring MVC servlet using the following syntax:

*

*

*

*

*

* @see CSRFRequestDataValueProcessor

*/

public class CSRFHandlerInterceptor extends HandlerInterceptorAdapter{

@Override

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

if (handler instanceof DefaultServletHttpRequestHandler) {

return true;

}

if (request.getMethod().equalsIgnoreCase("GET")) {

// GET - allow the request

return true;

} else {

// This is a POST request - need to check the CSRF token

String sessionToken = CSRFTokenManager.getTokenForSession(request.getSession());

String requestToken = CSRFTokenManager.getTokenFromRequest(request);

if (sessionToken.equals(requestToken)) {

return true;

} else {

response.sendError(HttpServletResponse.SC_FORBIDDEN, "Bad or missing CSRF value");

return false;

}

}

}

}

在bean配置文件中配置:

1

2

3

4

五、注意事项

如果项目web.xml中指定了error-page错误页面(比如403状态码对应的页面等),那么需要注意CSRFHandlerInterceptor拦截的问题。

如果验证成功,请求将沿着处理链继续传递;如果验证失败,请求将被暂停,发出一个HTTP 403的状态代码作为响应。但是问题来了,如果设置了error-page,Servlet 容器会先根据响应状态码将原始请求转发(forward)到具体的错误页面,然后发送到客户端浏览器。由于使用了拦截器,这次forward请求会再次被拦截,验证方法也会被触发,会再次验证失败。前端看不到403的错误提示页面。具体解决方案参见这篇文章[2]。

源码已放到Github上,有需要的童鞋请移步

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值