概述
作为为数不多的写在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中的结果一至!