cookie-parser
是Express的中间件,用来实现cookie的解析,是官方脚手架内置的中间件之一。javascript
它的使用很是简单,但在使用过程当中偶尔也会遇到问题。通常都是由于对Express + cookie-parser
的签名、验证机制不了解致使的。java
本文深刻讲解Express + cookie-parser
的签名和验证的实现机制,以及cookie签名是如何加强网站的安全性的。node
文本同步收录于GitHub主题系列《Nodejs学习笔记》git
先从最简单的例子来看下cookie-parser
的使用,这里采用默认配置。github
Express
的内置方法res.cookie()
。cookie-parser
中间件。var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
app.use(function (req, res, next) {
console.log(req.cookies.nick); // 第二次访问,输出chyingp
next();
});
app.use(function (req, res, next) {
res.cookie('nick', 'chyingp');
res.end('ok');
});
app.listen(3000);
复制代码
在当前场景下,cookie-parser
中间件大体实现以下:算法
app.use(function (req, res, next) {
req.cookies = cookie.parse(req.headers.cookie);
next();
});
复制代码
出于安全的考虑,咱们一般须要对cookie进行签名。express
例子改写以下,有几个注意点:安全
cookieParser
初始化时,传入secret
做为签名的秘钥。signed
设置为true
,表示对即将设置的cookie进行签名。req.cookies
,也能够经过req.signedCookies
获取。var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
// 初始化中间件,传入的第一个参数为singed secret
app.use(cookieParser('secret'));
app.use(function (req, res, next) {
console.log(req.cookies.nick); // chyingp
console.log(req.signedCookies.nick); // chyingp
next();
});
app.use(function (req, res, next) {
// 传入第三个参数 {signed: true},表示要对cookie进行摘要计算
res.cookie('nick', 'chyingp', {signed: true});
res.end('ok');
});
app.listen(3000);
复制代码
签名前的cookie值为chyingp
,签名后的cookie值为s%3Achyingp.uVofnk6k%2B9mHQpdPlQeOfjM8B5oa6mppny9d%2BmG9rD0
,decode后为s:chyingp.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0
。bash
下面就来分析下,cookie的签名、解析是如何实现的。cookie
Express完成cookie值的签名,cookie-parser
实现签名cookie的解析。二者共用同一个秘钥。
Express对cookie的设置(包括签名),都是经过res.cookie()
这个方法实现的。
精简后的代码以下:
res.cookie = function (name, value, options) {
var secret = this.req.secret;
var signed = opts.signed;
// 若是 options.signed 为true,则对cookie进行签名
if (signed) {
val = 's:' + sign(val, secret);
}
this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
return this;
};
复制代码
sign
为签名函数。伪代码以下,其实就是把cookie的原始值,跟hmac后的值拼接起来。
敲黑板划重点:签名后的cookie值,包含了原始值。
function sign (val, secret) {
return val + '.' + hmac(val, secret);
}
复制代码
这里的secret
哪来的呢?是cookie-parser
初始化的时候传入的。以下伪代码所示:
var cookieParser = function (secret) {
return function (req, res, next) {
req.secret = secret;
// ...
next();
};
};
app.use(cookieParser('secret'));
复制代码
知道了cookie签名的机制后,如何"解析"签名cookie就很清楚了。这个阶段,中间件主要作了两件事:
实现代码以下:
// str:签名后的cookie,好比 "s:chyingp.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0"
// secret:秘钥,好比 "secret"
function signedCookie(str, secret) {
// 检查是否 s: 开头,确保只对签过名的cookie进行解析
if (str.substr(0, 2) !== 's:') {
return str;
}
// 校验签名的值是否合法,如合法,返回true,不然,返回false
var val = unsign(str.slice(2), secret);
if (val !== false) {
return val;
}
return false;
}
复制代码
判断、提取cookie原始值比较简单。只是是unsign
方法名比较有迷惑性。
通常只会对签名进行合法校验,并无所谓的反签名。
unsign
方法的代码以下:
exports.unsign = function(val, secret){
var str = val.slice(0, val.lastIndexOf('.'))
, mac = exports.sign(str, secret);
return sha1(mac) == sha1(val) ? str : false;
};
复制代码
主要是出于安全考虑,防止cookie被篡改,加强安全性。
举个小例子来看下cookie签名是如何实现防篡改的。
基于前面的例子展开。假设网站经过nick
这个cookie来区分当前登陆的用户是谁。在前面例子中,登陆用户的cookie中,nick对应的值以下:(decode后的)
s:chyingp.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0
复制代码
此时,有人试图修改这个cookie值,来达到伪造身份的目的。好比修改为xiaoming
:
s:xiaoming.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0
复制代码
当网站收到请求,对签名cookie进行解析,发现签名验证不经过。由此可判断,cookie是伪造的。
hmac("xiaoming", "secret") !== "uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0"
复制代码
固然不是。
上个小节的例子,仅经过nick
这个cookie的值来判断登陆的是哪一个用户,这是一个很是糟糕的设计。虽然在秘钥未知的状况下,很难伪造签名cookie。但用户名相同的状况下,签名也是相同的。这种状况下,实际上是很容易伪造的。
另外,开源组件的算法是公开的,所以秘钥的安全性就成了关键,要确保秘钥不泄露。
还有不少,这里不展开。
本文主要对Express + cookie-parser
的签名和解析机制进行相对深刻的介绍。
很多相似的总结文章中,把cookie的签名说成了加密,这是一个常见的错误,读者朋友须要注意一下。
签名部分的介绍,稍微涉及一些简单的安全知识,对这块不熟悉的同窗能够留言交流。为讲解方便,部分段落、用词可能不够严谨。若有错漏,敬请指出。
https://github.com/expressjs/cookie-parser
https://github.com/chyingp/nodejs-learning-guide