我在 HTTP 请求标头和 POST 隐藏字段中发送 CSRF 令牌,以防止类似以下的 CSRF 攻击。
var request;
var timeout;
function insert(callback)
{
if(!request)
{
CKEDITOR.instances.txtAboutProducts.updateElement();
var contents=$("#txtAboutProducts").val();
var csrf_token=$("#token").val();
if(contents==null||contents=='')
{
alert("Please enter the contents.");
return;
}
request = $.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
datatype:"json",
type: "POST",
url: "../admin_side/AboutProducts.htm",
data: JSON.stringify({"contents":contents, "token":csrf_token}),
beforeSend: function (xhr)
{
xhr.setRequestHeader('X-CSRF-Token', csrf_token);
},
success: function(response)
{
callback(response);
},
complete: function()
{
timeout = request = null;
},
error: function(request, status, error)
{
if(status!=="timeout"&&status!=="abort") // or just if(status==="error")
{
alert(status+" : "+error);
}
callback(request);
}
});
timeout = setTimeout(function() {
if(request)
{
request.abort();
alert("The request has been timed out.");
}
}, 300000); //5 minutes.
}
}
此函数旨在插入由 CKEditor 保存的 CMS 内容,以便通过 JSON 对 Spring 的 POST AJAX 调用插入数据库(单击按钮时)。
if(!request){...}
函数体开头的条件检查只是为了防止重复的 AJAX 调用(可能是不耐烦的用户)。
一旦页面被加载并在 JavaScript 变量中检索,随机生成的令牌值就会存储在隐藏字段中。
var csrf_token=$("#token").val();
然后,以下处理程序设置一个与请求一起发送的标头。
beforeSend: function (xhr)
{
xhr.setRequestHeader('X-CSRF-Token', csrf_token);
},
为什么需要标头?单独作为隐藏字段发送的令牌是否不够?由于旧的 Flash Players 是否需要检查标题(仅作为示例)?
使用这种方法,在服务器端,我正在检查标头和隐藏字段的值是否匹配(以及检查会话中该令牌的存在{检查会话中令牌的存在无处虽然提到})。
上述 JavaScript 函数调用的 Spring 控制器类中的方法如下。
@RequestMapping(value=("admin_side/AboutProducts"), method=RequestMethod.POST)
private @ResponseBody CKEditorContentsHandler insert(@RequestBody final CKEditorContentsHandler object, final HttpServletResponse response, final HttpServletRequest request)
{
if(object!=null&&StringUtils.isNotBlank(object.getToken())&&StringUtils.isNotBlank(request.getHeader("X-CSRF-Token"))&&sessionTokenService.isTokenValid(object.getToken())&&object.getToken().equals(request.getHeader("X-CSRF-Token")))
{
aboutProductsService.insert(object.getContents());
object.setMessage("Insertion done successfully.");
object.setStatus(1);
}
else
{
object.setMessage("The authentication token cannot be verified.");
object.setStatus(-1);
}
return object;
}
其中CKEditorContentsHandler
,该方法的第一个参数是一个简单的 Java 类,它只包含几个属性来满足需求。
还建议将令牌存储在 cookie 中,并将其与发布的数据(隐藏字段)一起发送,并检查发布的数据和 cookie 值是否匹配。如果他们不这样做,那么可能是 CSRF 问题(因为攻击者无法读取或修改受害者浏览器上的 cookie,因为相同的源策略)。
之后,为什么需要标头?实际上,推荐的预防(或至少减轻)CSRF 攻击的方法是什么?
以下答案很好地总结了它。