看了一篇文章:基于timestamp和nonce的防止重放攻击方案,动手用代码实现了一下,感觉还不错。
表单加载
表单加载时执行获取时间戳、随机数、数字签名并发送到客户端:
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String token = "security.ra.token";
// 使用请求头发送时间戳、随机数、数字签名
ModifyHttpServletRequestWrapper httpServletRequestWrapper = new ModifyHttpServletRequestWrapper(req);
long stime = System.currentTimeMillis();
String nonce = UUID.randomUUID();
String sign = getMD5(token, stime, nonce);//MD5加密(具体方法不再赘述)
httpServletRequestWrapper.putHeader("stime",String.valueOf(stime));// 时间戳
httpServletRequestWrapper.putHeader("nonce", nonce);// 随机数
httpServletRequestWrapper.putHeader("sign", sign);// 数字签名
表单提交
表单提交时验证时间戳、随机数、数字签名:
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String token = "security.ra.token";
//获取客户端提交的隐藏域数据
String stime = req.getParameter("stime");
String nonce = req.getParameter("nonce");
String sign = req.getParameter("sign");
token = getMD5(token, stime, nonce);//获取MD5加密的数字签名(具体方法不再赘述)
long systemTime = System.currentTimeMillis();
try {
// 这里可优化为提前返回,避免if嵌套,导致代码不美观,可读性差
if(token.equals(sign)) {//验证签名
if(systemTime - Long.parseLong(stime) <= 60000) {//验证时间戳,超过60s表示超时
if(!query(nonce)) {//验证随机数是否已存在,即查询数据库表中是否已含有此随机数,存在则说明已提交
delete(systemTime );//删除当前时间60s以前存储的数据(具体方法不再赘述)
add(Long.parseLong(stime), nonce, sign);//增加新的随机数数据(具体方法不再赘述)
//满足则放行
...
}
}
}
} catch(Throwable e) {
//打印日志...
}
对于重放攻击,斯以为这是一种比较好的解决方案,虽然仍可通过模拟客户端的方式模拟正常请求,但成本已成倍增加,得不偿失;另外,也可以考虑使用IP限制等等。