(1)合规方案
为交易添加序号,用来保证交易的唯一性。序号生成方法可以选择随机数、时间戳、或递增数。
(2)安全编码示例:
A.序列号防重放实现
客户端向服务器端发起请求,获取初始序列号。
客户端携带序列号向服务器发起请求。
var get_serialnum = $request({
'type': 'GET',
'url': '/access_serialnum'
});
var create_req = function (request) {
// post
return get_token({uid: 1001}).then(function (serialnum) {
return $request({
'type': 'POST',
'url': '/books',
// 将序列号放入请求头部,每次请求+1
'headers': {
'serialnummber': serialnum.serialnumber + 1
}
})(request);
});
}
var book = {name: '如何变得有思想', author: '阮一峰', publisher: '人民邮电出版社'};
create_req(book).then(function (res) {
console.log(res);
// 200 ok! book created
});
服务器端通过实现过滤器,对除获取序列号之外的所有请求验证服务器端存储的序列号与客户端提交的是否匹配。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String serialNum = req.getHeader("serialnummber");
if (String.isNullOrEmpty(serialNum)) {
res.getWriter().write("该请求已失效");
return;
}
long reqnum = Long.parseLong(serialNum);
long servnum = Long.parseLong(req.getSession().getAttribute("serialnummber")) + 1
//若请求中携带的序列号小于或等于服务器端存储的经过计算后的序列号值,则判定为非法请求
if (reqTime <= servnum) {
res.getWriter().write("该请求已失效");
return;
}
req.getSession().setAttribute("serialnummber", servnum);
chain.doFilter(request, response);
}
B.时间戳防重放实现
public class ReplayFilter implements Filter {
private long appTimeStamp = -1;
public ReplayFilter() {
// TODO Auto-generated constructor stub
}
public void destroy() {
appTimeStamp = -1;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
long reqTime = Long.parseLong(req.getParameter("timestamp"));
long serverTime = System.currentTimeMillis();
//判断请求时间是否有效
if (reqTime < serverTime + appTimeStamp) {
res.getWriter().write("该请求已失效");
return;
}
chain.doFilter(request, response);
}
public void init(FilterConfig fConfig) throws ServletException {
//从配置文件中获取时间间隔,间隔值越大,越能包容网络延时,间隔值越小,越能防护重放攻击
appTimeStamp = APPHelper.getTimeStamp();
}
}
C.挑战应答机制防重放实现(挑战应答机制:每次认证时认证端都给被认证端发送一个不同的”挑战”字串,被认证端收到这个”挑战”字串后,做出相应的”应答”)
客户端调用CryptoJS加密类库的HMAC-SHA256加密算法,以随机数作为密钥,对用户提交数据(用户密码)进行加密,示例代码如下:
<script src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/hmac-sha256.js"></script>
<script type="text/javascript">
//获取服务器返回的随机值
var randomNum = getRandom();
$(function () {
$.ajax({
type: 'post',
url: 'xxxx.do',
dataType: 'json',
data: {
//对密码进行HMAC运算
'password': +CryptoJS.HmacSHA256($('#pwd').val(), randomNum)
},
success: function (data) {
if (data != null && data.length > 0) {
//这里该怎么写
}
},
error: function () {
$.message.alert('提示', '请求失败!', 'error');
}
});
})
;
</script>
服务器端从数据库中读取数据(用户密码),使用session中的随机数作为密钥进行HMAC-SHA256加密运算后进行匹配,校验通过后,则进行下一步操作。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter(“user”);
String hmacPwd = request.getParameter(“password”);
String password = getPasswordFromDB(username);
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(password.getBytes(), “HmacSHA256”);
sha256_HMAC.init(secret_key);
if (hmacPwd.equals(sha256_HMAC.dofinal())) {
//验证通过,处理业务
}
}