在上一篇博文中以及对于HTTP协议的第二种安全认证方式即摘要认证的流程和详细的头部定义都进行了归纳总结。本文中主要是依照上文描述的HTTP摘要认证协议实现一个安全认证的实例,在实践中体会HTTP对于安全方面的考虑以及其在安全方面的局限性。
摘要认证不像基本认证那样被广泛实现,在摘要认证的例子需要自己做一个客户端接受服务器的消息并解析其头部信息,然后根据返回的状态码确定应该如何应对(401-未认证;200-正常获取到数据)
照例引用一张书中的图来形象的说明摘要认证的过程(图13-2)
首先是客户端获取到html文件,并且在页面中需要向服务器端发送数据请求。
$.ajax({
type: 'GET',
url: 'http://localhost:9000/customer/id/10000',//url只是个例子,任意的数据请求都应该被验证
data: {
'res': 'oh i want you'
},
success: function(data, textStatus, jqxhr) {
// do something
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
//do something
firstHeader = utils.GetInfos(XMLHttpRequest.getResponseHeader("WWW-Authenticate"));
if (XMLHttpRequest.status == 401) {
$("#auth").show();
}
}
});
客户端向服务器端发送请求(data中的数据是随便写的,有没有都可以),希望服务器端通过restful接口返回某个编号为10000的客户的数据。
其次,也就是摘要认证中的第二步,服务器经过验证,这个restful接口需要授权并需要验证信息,因而直接返回了401的状态码并附带认证所需要传递的信息,比如realm、nonce等。
服务器端代码片段
function verifyAuthentication(req, res) {
if (IsNeedAuthentication(req)) {
res.writeHead(401, {
'content-Type': 'text/plain',
'WWW-Authenticate': ['Digest', 'realm="Shopping Cart"',
'qop="auth,auth-int"', 'nonce="' + utils.CreateNonce(req.url) + '"',
'opaque="' + base64.encoder('346536465') + '"'
].join(' ')
});
res.end('需要认证');
} else {
res.writeHead(200, {
'content-Type': 'text/plain',
'Authentication-Info': [
'nextnonce=' + guid.NewGuid(), 'cnonce="' + utils.GetInfo(req.headers['authorization'], "cnonce") + '"'
].join(' ')
});
res.end('获取到了数据');
}
}
//guid.NewGuid()是个用随机数模拟GUID的函数,参考2和3
//utils.GetInfo()是个获取请求头部信息的函数
//base64.encoder()是个计算base64编码的函数,参考5
function IsNeedAuthentication(req) {
var userName = GetUserName(req);//获取用户名
if (userName) {
var pwd = GetPassword(req);//获取客户端发送的验证信息,由密码等混合后摘要所得
var digest = GetDigest(req);//服务器端根据相同的摘要算法计算摘要
return CheckPassword(pwd, digest);//比较pwd和digest是否相同
} else {
return true;
}
}
通过上文中的代码片段可以看出,服务器端需要获取到用户的HTTP请求中的头部验证信息(在authorization首部中),并根据客户端和服务器端约定好的摘要计算公式计算认证信息。没有通过就返回401,验证通过就返回200并附带上数据一并返回。当然401和200时所携带的首部分别是放在WWW-Authenticate和Authentication-Info中的。
第三步,客户端获取到服务器发送的401码,明白服务器端需要验证信息,因而根据协议要求,使用约定的摘要计算公式,计算安全认证信息。
$.ajax({
url: "http://localhost:9000/customer/id/10000",
headers: {
"Authorization": constructResAuth()
},
success: function() {
alert('success');
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
}
});
function constructResAuth() {
var user = $("#name").val();
var pwd = $("#pwd").val();
var cnonce = Guid.NewGuid().ToString('N');
var uri = "http://localhost:9000/customer/id/10000";
var response = utils.CreateResponse(user, pwd, cnonce, uri, firstHeader);
var auth = ["Digest",
"username=\"" + user + "\"",
"realm=\"" + firstHeader["realm"] + "\"",
"nonce=\"" + firstHeader["nonce"] + "\"",
"uri=\"" + uri + "\"",
"cnonce=\"" + cnonce + "\"",
"response=\"" + response + "\"",
'opaque=\"' + firstHeader['opaque'] + '\"',
'nc="00000001"'].join(' ');
return auth;
}
之后就是第四步,服务器验证并返回验证结果,代码段可以参考第二步的内容。
从上文中可以看到客户端在返回验证信息时和服务器端应答验证请求时在首部都附加了许多信息,这些信息在HTTP摘要认证中都是有作用的。有的是用于防止重放攻击(Replay Attacks),有的是防止篡改报文的,有的是支持多重验证的,有的是防止词典攻击,有的是防止中间人攻击的等等。由此可见,摘要认证确实比基本认证复杂许多,也安全了许多,在许多情况下,摘要认证就可以做到大多数的安全防护功能。摘要认证的协议内容的使用的好坏仍然取决于使用者的水平,并不是使用了摘要认证就绝对安全,就算是SSL或者TLS也不敢说是绝对安全的。在一般的应用场景中,正确使用摘要认证,只要能减少绝大多数恶意用户和攻击者得手的可能性,并增加他们攻击的复杂度,使得系统在安全和性能方面的收益远高于风险就完全可以满足应用的需要。
参考:
1. Http权威指南
2. JavaScript生成GUID的算法
3. 使用js生成GUID
4. 博文共赏:Node.js静态文件服务器实战
5. javascript base64 encode and decode
6. 详解HTTP中的摘要认证机制