Java 代码示例
下文将以 Java 为例,对上述三种方法分别用代码进行示例。无论使用何种方法,在服务器端的拦截器必不可少,它将负责检查到来的请求是否符合要求,然后视结果而决定是否继续请求或者丢弃。在 Java 中,拦截器是由 Filter 来实现的。我们可以编写一个 Filter,并在 web.xml 中对其进行配置,使其对于访问所有需要 CSRF 保护的资源的请求进行拦截。
在 filter 中对请求的 Referer 验证代码如下
清单 1. 在 Filter 中验证 Referer
// 从 HTTP 头中取得 Referer 值
String referer=request.getHeader("Referer");
// 判断 Referer 是否以 bank.example 开头
if((referer!=null) &&(referer.trim().startsWith(“bank.example”))){
chain.doFilter(request, response);
}else{
request.getRequestDispatcher(“error.jsp”).forward(request,response);
}
以上代码先取得 Referer 值,然后进行判断,当其非空并以 bank.example 开头时,则继续请求,否则的话可能是 CSRF 攻击,转到 error.jsp 页面。
如果要进一步验证请求中的 token 值,代码如下
清单 2. 在 filter 中验证请求中的 token
HttpServletRequest req = (HttpServletRequest)request;
HttpSession s = req.getSession();
// 从 session 中得到 csrftoken 属性
String sToken = (String)s.getAttribute(“csrftoken”);
if(sToken == null){
// 产生新的 token 放入 session 中
sToken = generateToken();
s.setAttribute(“csrftoken”,sToken);
chain.doFilter(request, response);
} else{
// 从 HTTP 头中取得 csrftoken
String xhrToken = req.getHeader(“csrftoken”);
// 从请求参数中取得 csrftoken
String pToken = req.getParameter(“csrftoken”);
if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){
chain.doFilter(request, response);
}else if(sToken != null && pToken != null && sToken.equals(pToken)){
chain.doFilter(request, response);
}else{
request.getRequestDispatcher(“error.jsp”).forward(request,response);
}
}
首先判断 session 中有没有 csrftoken,如果没有,则认为是第一次访问,session 是新建立的,这时生成一个新的 token,放于 session 之中,并继续执行请求。如果 session 中已经有 csrftoken,则说明用户已经与服务器之间建立了一个活跃的 session,这时要看这个请求中有没有同时附带这个 token,由于请求可能来自于常规的访问或是 XMLHttpRequest 异步访问,我们分别尝试从请求中获取 csrftoken 参数以及从 HTTP 头中获取 csrftoken 自定义属性并与 session 中的值进行比较,只要有一个地方带有有效 token,就判定请求合法,可以继续执行,否则就转到错误页面。生成 token 有很多种方法,任何的随机算法都可以使用,Java 的 UUID 类也是一个不错的选择。
除了在服务器端利用 filter 来验证 token 的值以外,我们还需要在客户端给每个请求附加上这个 token,这是利用 js 来给 html 中的链接和表单请求地址附加 csrftoken 代码,其中已定义 token 为全局变量,其值可以从 session 中得到。
清单 3. 在客户端对于请求附加 token
function appendToken(){
updateForms();
updateTags();
}
function updateForms() {
// 得到页面中所有的 form 元素
var forms = document.getElementsByTagName('form');
for(i=0; i<forms.length; i++) {
var url = forms[i].action;
// 如果这个 form 的 action 值为空,则不附加 csrftoken
if(url == null || url == "" ) continue;
// 动态生成 input 元素,加入到 form 之后
var e = document.createElement("input");
e.name = "csrftoken";
e.value = token;
e.type="hidden";
forms[i].appendChild(e);
}
}
function updateTags() {
var all = document.getElementsByTagName('a');
var len = all.length;
// 遍历所有 a 元素
for(var i=0; i<len; i++) {
var e = all[i];
updateTag(e, 'href', token);
}
}
function updateTag(element, attr, token) {
var location = element.getAttribute(attr);
if(location != null && location != '' '' ) {
var fragmentIndex = location.indexOf('#');
var fragment = null;
if(fragmentIndex != -1){
//url 中含有只相当页的锚标记
fragment = location.substring(fragmentIndex);
location = location.substring(0,fragmentIndex);
}
var index = location.indexOf('?');
if(index != -1) {
//url 中已含有其他参数
location = location + '&csrftoken=' + token;
} else {
//url 中没有其他参数
location = location + '?csrftoken=' + token;
}
if(fragment != null){
location += fragment;
}
element.setAttribute(attr, location);
}
}
在客户端 html 中,主要是有两个地方需要加上 token,一个是表单 form,另一个就是链接 a。这段代码首先遍历所有的 form,在 form 最后添加一隐藏字段,把 csrftoken 放入其中。然后,代码遍历所有的链接标记 a,在其 href 属性中加入 csrftoken 参数。注意对于 a.href 来说,可能该属性已经有参数,或者有锚标记。因此需要分情况讨论,以不同的格式把 csrftoken 加入其中。
如果你的网站使用 XMLHttpRequest,那么还需要在 HTTP 头中自定义 csrftoken 属性,利用 dojo.xhr 给 XMLHttpRequest 加上自定义属性代码如下:
清单 4. 在 HTTP 头中自定义属性
var plainXhr = dojo.xhr;
// 重写 dojo.xhr 方法
dojo.xhr = function(method,args,hasBody) {
// 确保 header 对象存在
args.headers = args.header || {};
tokenValue = '<%=request.getSession(false).getAttribute("csrftoken")%>';
var token = dojo.getObject("tokenValue");
// 把 csrftoken 属性放到头中
args.headers["csrftoken"] = (token) ? token : " ";
return plainXhr(method,args,hasBody);
};
这里改写了 dojo.xhr 的方法,首先确保 dojo.xhr 中存在 HTTP 头,然后在 args.headers 中添加 csrftoken 字段,并把 token 值从 session 里拿出放入字段中。