阅读数:

nodejs api实现数字签名验证

0

说明

nodejs作为服务端api开发语言已经不是什么稀奇的事情了,今天分享的不是怎么利用express
进行api封装,而是怎么利用middleware中间件对api接口进行签名验证。为什么要做签名验证,
原因很简单就是为了安全。

实现

大家都知道express route可以很灵活的加入一些中间件包括我们自定义的,今天要实现的数字签名
验证需要依赖passport-digital-signature进行二次开发。
签名逻辑说明:对每个api请求必须要求传入时间戳timestamp、keyId和数字签名值sign。其中
sign签名规则为:

第一步:

设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:

参数名按ASCII码从小到大排序(字典序);
如果参数的值为空不参与传参和签名;
参数名区分大小写;
get 和 post 参数均参与签名(注意:签名时不要对 get 和 post 的参数进行urlencode);
sign 参数不参与签名;
根据HTTP协议要求,传递参数的值中如果存在特殊字符(如:&、@等),那么该值需要做URL Encoding,这样请求接收方才能接收到正确的参数值。这种情况下,待签名数据应该是原始值而不是encoding之后的值。例如:调用某接口需要对请求参数email进行数字签名,那么待签名数据应该是email=test@msn.com,而不是email=test%40msn.com。

第二步:

在stringA最后拼接上“&key=AppSecret”得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值。

第三步:

拼接sign签名,得到最终请求字符串。

具体实现步骤:

  • 1、npm install passport-digital-signature
  • 2、修改passport-digital-signature签名逻辑
    进入passport-digital-signature/lib目录,打开strategy.js,实现签名和签名验证规则,核心方法:
    2.1、param排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 获取参数字符串 ascall码排序
*/
function getVerifyParams(params) {
var sPara = [];
if(!params) return null;
for(var key in params) {
if((!params[key]) || key == "sign") {
continue;
};
sPara.push([key, params[key]]);
}
sPara = sPara.sort();
var prestr = '';
for(var i2 = 0; i2 < sPara.length; i2++) {
var obj = sPara[i2];
if(i2 == sPara.length - 1) {
prestr = prestr + obj[0] + '=' + obj[1] + '';
} else {
prestr = prestr + obj[0] + '=' + obj[1] + '&';
}
}
prestr += '&key='+params.keyId;
return prestr;
};

2.2、生成签名

1
2
3
4
5
6
7
8
9
10
11
/**
* 获取签名
*/
function getSign(data){
if (data && data.length > 0) {
var buf = new Buffer(data);
var str = buf.toString('binary');
let md5Str = crypto.createHash('md5').update(str).digest('hex');
return md5Str.toUpperCase();
}
}

2.3 overwrite authenticate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* Authenticate request based on the contents of a form submission.
*
* @param {Object} request
* @api protected
*/
Strategy.prototype.authenticate = function(req, options) {
//时间戳timestamp、keyId和数字签名值sign 三个参数必传
if (!req.query || !req.query.sign || !req.query.timestamp || !req.query.keyId) {
return this.fail2(1005);
}
var timestamp = req.query.timestamp;
var now = new Date().getTime();
//timestamp 必须是10位或者13位
if(timestamp.length != 10 && timestamp.length != 13){
return this.fail2(1001);
}else if(timestamp.length == 10){
timestamp = timestamp * 1000;
}
let grap = now - timestamp ;
//timestamp 3分钟内有效
if(grap > 3 * 60 * 1000 || grap <= 0){
return this.fail2(1001);
}
var appkey = req.query.keyId;
var params = req.query;
var self = this;
function verified(err, client) {
if (err) { return self.fail2(-1); }
// keyId不存在
if (!client) { return self.fail2(1006); }
// 获取排序参数
params = getVerifyParams(params);
// 获取正确签名
var signVerfied = getSign(params);
var sign = req.query.sign;
var flag = false;
flag = verifySign(signVerfied,sign);
if(!flag){
return self.fail2(1002);
}
self.pass();
}
if (self._passReqToCallback) {
this._verify(req, appkey, verified);
} else {
this._verify(appkey, verified);
}
};
  • 3、api router注册
1
2
3
4
5
const DigitalSignatureStrategy =require('passport-digital-signature').Strategy;
// 验证keyId是否有效
const digitalSignature = require('./middleware/checkClient');
passport.use(new DigitalSignatureStrategy(digitalSignature));
app.use('/openapi', passport.authenticate('digital-signature'), yourRoute);

测试

digit1
digit2
digit3


^-^欢迎回复交流^-^


0
赏点咖啡钱^.^