概述
作为为数不多的写在Koa实例中的功能,Cookies防篡改机制通过接口app.keys暴露给开发者。该接口接收两类参数:
- 密钥:
['secret1', 'secret2', 'secret3']或'secret',字符串或数组; - Keygrip实例:
app.keys = new Keygrip(["SEKRIT2", "SEKRIT1"], 'sha256', 'hex')。
在防篡改机制的保护下,存放在Cookies中的数据如果被人为修改,就会被检测出来,不再生效。
Cookies防篡改机制为:
- 服务器提供一个签名生成算法
sign - 对数据进行签名
sign(key)得到密文保存为key.sig - 将
key和key.sig同时存在Cookie中,客户端每一次请求都带着key和key.sig - 服务器收到请求后,校验
key和key.sig,检验内容是否被篡改
Keygrip使用方法
安装:
1 | npm i keygrip -S |
初始化Keygrip对象:
1 | const keys = new Keygrip(keylist, [hmacAlgorithm], [encoding]) |
keylist:字符串数组,必填,用于签名的密钥列表;hmacAlgorithm:用于签名的算法,默认为sha1,该参数用于指定node.js自带的crypto模块的createHmac()方法的第一个参数,取决于平台上OpenSSL版本支持的可用算法。例如sha1,sha256,sha512,md5, …;encoding:编码方式,默认为base64,可选:utf8,base64,hex,ascii,binary, …。
使用Keygrip计算hash值、验证hash、匹配列表中的密钥。
1 | const Keygrip = require('keygrip'); |
Keygrip的核心是三个函数:
sign(data):使用密钥列表中的第一个密钥对数据进行签名;verify(data, hash):验证数据是否被篡改,计算data的hash值,与hash进行对比;index(data, hash):验证hash是否是由data经列表中的密钥签名获得,如果不是,返回-1,如果是,返回密钥在列表中的序号。
Koa.js中Cookies防篡改的方法
Koa.js中使用pillarjs/cookies来管理Cookies,可以通过ctx.cookies访问到这个对象的实例,其中最重要的两个方法是get()和set()。
ctx.cookies.set(key, value, opts)ctx.cookies.get(key, opts)
opts的完整参数可以查看仓库API或源码,这里我们只需要用到其最常用的signed,一个使用签名防止cookie篡改的例子如下所示:
1 | /** |
这样,当访问服务器后,会在cookie中留下两个字段:_user和_user.sig,分别存放的是数据和数据签名:
_user = {"account":"test","password":"123456"}
_user.sig = 8onPj6-OdNwq2N9ON52GtkEZBX0
要验证一个cookie是否被篡改,需要用到ctx.cookies.get()函数:
1 | ctx.cookies.get('_user', { signed: true }) |
如果数据不存在或者被篡改,则该语句的返回值为null。
做了个小测试,想看看cookie中保存的签名字段到底是什么的签名,做了如下测试:
1 | const Keygrip = require('keygrip'); |
结果发现输出结果是:J-89cUmdlRoSwdmub_5gBhLHdaQ,这和保存在cookie中的8onPj6-OdNwq2N9ON52GtkEZBX0不一致啊!
后来到Cookies的仓库pillarjs/cookies翻了一下源码,找到两处语句:
1 | if (opts && signed) { |
1 | Cookie.prototype.toString = function() { |
受此启发,将签名的字符串改为_user={"account":"test","password":"123456"},再进行签名:
1 | const Keygrip = require('keygrip'); |
得到结果:8onPj6-OdNwq2N9ON52GtkEZBX0,与保存在cookie中的结果一至!