CSRF功能

作用:每次请求不能重复提交,防止黑客通过URL请求窃取数据

原理:每次请求都携带 随机token(该token在后台产生), 携带的token与后台的token进行对比,若相同,表示请求合法,否则请求不合法。

  1. token算法:

public static String getRandomString(int length) { // length 字符串长度
StringBuffer buffer = new StringBuffer(“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”);
StringBuffer sb = new StringBuffer();
SecureRandom r = new SecureRandom();
int range = buffer.length();
for (int i = 0; i < length; i++) {
sb.append(buffer.charAt(r.nextInt(range)));
}
return sb.toString();
}

2.token存在何处:

后端:每次生成新的token,存放在 session中(用于token对比), response头消息也存放个token(用于传给前端)

public static void setSessionToken(HttpServletRequest req,HttpServletResponse resp) {
String csrfToken = getRandomString(30);
req.getSession().setAttribute(AJAX_CSRF_TOKEN, csrfToken); //token放入session中
resp.setHeader(“Ajax-Param-Random”,csrfToken); //token放入response头消息中
}
前端: 获取的token存放在 request头消息(ajax请求)中 或者 Form表单中(form表单请求)

ajax形式:

var ajaxCsrfToken = “${sessionScope.token}”; // 页面初始化的token
$.ajaxSetup({
type: “post”,
global: false,
beforeSend: function (xhr) {
xhr.setRequestHeader(‘token’, ajaxCsrfToken); //提交请求携带的token
},
complete: function (xhr) {
if(xhr.getResponseHeader(“Ajax-Param-Random”) != null) {
ajaxCsrfToken = xhr.getResponseHeader(“Ajax-Param-Random”); //提交消息结束后,本窗口的token刷新
var curName = window;
while(curName.parent && curName.parent.name != curName.name) { //多个父窗口的token刷新
curName.parent.ajaxCsrfToken = xhr.getResponseHeader(“Ajax-Param-Random”);
curName = curName.parent;
}
}
}
});
form表单形式:

a. 跳转页面超链接改为post形式:

function postFancybox(url, params, options) {
options = !!options ? options : {};
var $a = $("

        var $form = $("<form></form>").attr({"method": "post", "action": url, "target": iframeName});
        params && $.each(params, function (key, value) {
            var $input = $("<input type='hidden'/>");
            $input.attr("name", key).val(value);
            $form.append($input);
        });
        var $input = $("<input type='hidden'/>");
        $input.attr("name", "csrfToken").val(ajaxCsrfToken);
        $form.append($input);
        $form.appendTo('body').submit().remove();
    }
}, options)).trigger("click").remove();
refreshToken();

}

b. 导出的post形式

function exportExl(url, params, fileName) {
var inputs = “”;
for(var key in params) {
var param_value = params[key];
inputs += “”;
}
inputs += “”;
inputs += “”;
var form_str = “” + inputs + “”;
$(form_str).appendTo(‘body’).submit().remove();
refreshToken();
}

注:凡是表单提交,都要进行token刷新。 (向后台获取token需要带当前表单提交的token,保证token刷新也不能重复提交) 问题:表单提交的token为何还能用,后台token难道不更新吗?----采用缓存,见步骤 4

function refreshToken() {
$.ajax({
url : “/token_refresh.action”,
type: “post”,
beforeSend: function (xhr) {
xhr.setRequestHeader(‘token’,ajaxCsrfToken);
},
complete: function (xhr) {
if(xhr.getResponseHeader(“Ajax-Param-Random”) != null) {
ajaxCsrfToken = xhr.getResponseHeader(“Ajax-Param-Random”);
var curName = window;
while(curName.parent && curName.parent.name != curName.name) {
curName.parent.ajaxCsrfToken = xhr.getResponseHeader(“Ajax-Param-Random”);
curName = curName.parent;
}
}
}
});
}

  1. token如何比较

前端传的token在request头消息中或者form表单中。前端传的token与后端里的session的token进行比较。若相同则合法,否则非法

public static synchronized boolean sessionEqualsAjax(HttpServletRequest req,HttpServletResponse resp){
String sessionCsrfToken = String.valueOf(req.getSession().getAttribute(SessionCookie.AJAX_CSRF_TOKEN)); //获取session里的token
String formCsrfToken = req.getParameter(FORM_CSRF_TOKEN); //获取form表单的token
String headerToken = req.getHeader(SessionCookie.AJAX_CSRF_TOKEN); //获取request头消息里的token
String requestToken = StringUtils.isNotBlank(headerToken)?headerToken:formCsrfToken; //设置前端传的token
String tokenCacheValue;
try {
tokenCacheValue = ajaxTokenCache.get(requestToken, () -> { //前端传的token是否缓存中存在
return “0”;
});
} catch (ExecutionException e) {
tokenCacheValue = “0”;
}
if (“1”.equals(tokenCacheValue)) { //前端传的token是否缓存中存在,若存在,不用比较直接通过(该token可能不是最新的token,但缓存中存在)
resp.setHeader(“Ajax-Param-Random”,sessionCsrfToken); //将session里的token更新到response头消息中
ajaxTokenCache.put(sessionCsrfToken,“1”); //将session里的token更新到缓存中
return true;
} else if (sessionCsrfToken.equalsIgnoreCase(requestToken)) { //前端传的token在缓存中不存在,进行token比较
ajaxTokenCache.put(requestToken,“1”); //将当前请求的token更新到缓存中
setSessionToken(req,resp); //生成新的token
return true;
}
return false;
}

  1. 何时生成新的token。

登录成功后生成新的token

每次请求比较token成功后,再生成新的token(具体细节见步骤 3)

  1. 如何解决异步请求同时发消息(token值相同问题)

建个缓存,token不立即失效,在3秒之内提交的token可以相同

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

public static Cache<String, String> ajaxTokenCache = CacheBuilder.newBuilder()
.expireAfterAccess(tokenCacheTimeOut, TimeUnit.SECONDS)
.maximumSize(tokenMaximumSize)
.build();
(具体何时更新缓存,细节见 步骤 3)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值