Skip to content

Commit d16e0fa

Browse files
committed
新增文章:cookie-parser深入剖析
1 parent 37ccc6d commit d16e0fa

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Nodejs学习过程中的一些总结,可[点击这里](https://github.com/chyi
5555
* [本地调试远程服务器上的Node代码(ok)](/模块/debug.md)
5656
* [express+session实现简易身份认证(ok)](进阶/express+session实现简易身份认证.md)
5757
* [express+morgan:从入门使用到源码剖析(ok)](进阶/日志模块morgan.md)
58+
* [express+cookie-parser:签名机制深入剖析(ok)](进阶/cookie-parser-deep-in.md)
5859
* [Nodejs 进阶:log4js入门实例(ok)](进阶/log4js.md)
5960
* [调试日志打印:debug模块](/进阶/debug-log.md)
6061
* [Nodejs进阶:crypto模块之理论篇](/进阶/crypto-theory.md)

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
* [本地调试远程服务器上的Node代码(ok)](/模块/debug.md)
4848
* [express+session实现简易身份认证(ok)](进阶/express+session实现简易身份认证.md)
4949
* [express+morgan:从入门使用到源码剖析(ok)](进阶/日志模块morgan.md)
50+
* [express+cookie-parser:签名机制深入剖析(ok)](进阶/cookie-parser-deep-in.md)
5051
* [Nodejs 进阶:log4js入门实例(ok)](进阶/log4js.md)
5152
* [调试日志打印:debug模块](/进阶/debug-log.md)
5253
* [Nodejs进阶:crypto模块之理论篇](/进阶/crypto-theory.md)

进阶/cookie-parser-deep-in.md

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
## 文章导读
2+
3+
`cookie-parser`是Express的中间件,用来实现cookie的解析,是官方脚手架内置的中间件之一。
4+
5+
它的使用非常简单,但在使用过程中偶尔也会遇到问题。一般都是因为对`Express + cookie-parser`的签名、验证机制不了解导致的。
6+
7+
本文深入讲解`Express + cookie-parser`的签名和验证的实现机制,以及cookie签名是如何增强网站的安全性的。
8+
9+
## 入门例子:cookie设置与解析
10+
11+
先从最简单的例子来看下`cookie-parser`的使用,下面采用默认配置。
12+
13+
* cookie设置:使用`Express`的内置方法`res.cookie`
14+
* cookie解析:使用`cookie-parser`中间件。
15+
16+
```javascript
17+
var express = require('express');
18+
var cookieParser = require('cookie-parser');
19+
var app = express();
20+
21+
app.use(cookieParser());
22+
23+
app.use(function (req, res, next) {
24+
console.log(req.cookies.nick); // 第二次访问,输出chyingp
25+
next();
26+
});
27+
28+
app.use(function (req, res, next) {
29+
res.cookie('nick', 'chyingp');
30+
res.end('ok');
31+
});
32+
33+
app.listen(3000);
34+
```
35+
36+
在当前场景下,`cookie-parser`中间件大致实现如下:
37+
38+
```javascript
39+
app.use(function (req, res, next) {
40+
req.cookies = cookie.parse(req.headers.cookie);
41+
next();
42+
});
43+
```
44+
45+
## 进阶例子:cookie签名与解析
46+
47+
出于安全的考虑,我们通常需要对cookie进行签名。
48+
49+
例子改写如下,有两个注意点:
50+
51+
1. `cookieParser`初始化时,传入`secret`作为签名的秘钥。
52+
2. 设置cookie时,将`signed`设置为`true`,表示对cookie进行签名。
53+
2. 获取cookie时,可以同时通过`req.cookies`,也可以通过`req.signedCookies`获取。
54+
55+
```javascript
56+
var express = require('express');
57+
var cookieParser = require('cookie-parser');
58+
var app = express();
59+
60+
// 初始化中间件,传入的第一个参数为singed secret
61+
app.use(cookieParser('secret'));
62+
63+
app.use(function (req, res, next) {
64+
console.log(req.cookies.nick); // chyingp
65+
console.log(req.signedCookies.nick); // chyingp
66+
next();
67+
});
68+
69+
app.use(function (req, res, next) {
70+
// 传入第三个参数 {signed: true},表示要对cookie进行摘要计算
71+
res.cookie('nick', 'chyingp', {signed: true});
72+
res.end('ok');
73+
});
74+
75+
app.listen(3000);
76+
```
77+
78+
签名前的cookie值为`chyingp`,签名后的cookie值为`s%3Achyingp.uVofnk6k%2B9mHQpdPlQeOfjM8B5oa6mppny9d%2BmG9rD0`
79+
80+
下面就来分析下,cookie的签名、解析是如何实现的。
81+
82+
## cookie签名、解析实现剖析
83+
84+
Express完成cookie值的签名,`cookie-parser`实现签名cookie的解析。两者公用同一个秘钥。
85+
86+
### cookie签名
87+
88+
Express对cookie的设置(包括签名),都是通过`res.cookie`这个方法实现的。
89+
90+
精简后的代码如下:
91+
92+
```javascript
93+
res.cookie = function (name, value, options) {
94+
var secret = this.req.secret;
95+
var signed = opts.signed;
96+
97+
// 如果 options.signed 为true,则对cookie进行签名
98+
if (signed) {
99+
val = 's:' + sign(val, secret);
100+
}
101+
102+
this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
103+
104+
return this;
105+
};
106+
```
107+
108+
`sign`为签名函数。伪代码如下,其实就是把cookie的原始值,跟hmac后的值拼接起来。
109+
110+
>敲黑板划重点:签名后的cookie值,包含了原始值。
111+
112+
```javascript
113+
function sign (val, secret) {
114+
return val + '.' + hmac(val, secret);
115+
}
116+
```
117+
118+
这里的`secret`哪来的呢?是`cookie-parser`初始化的时候传入的。如下伪代码所示:
119+
120+
```javascript
121+
var cookieParser = function (secret) {
122+
return function (req, res, next) {
123+
req.secret = secret;
124+
// ...
125+
next();
126+
};
127+
};
128+
129+
app.use(cookieParser('secret'));
130+
```
131+
132+
### 签名cookie解析
133+
134+
知道了cookie签名的机制后,如何"解析"签名cookie就很清楚了。这个阶段,中间件主要做了两件事:
135+
136+
1. 将签名cookie对应的原始值提取出来
137+
2. 验证签名cookie是否合法
138+
139+
实现代码如下:
140+
141+
```javascript
142+
// str:签名后的cookie,比如 "s:chyingp.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0"
143+
// secret:秘钥,比如 "secret"
144+
function signedCookie(str, secret) {
145+
146+
// 检查是否 s: 开头,确保只对签过名的cookie进行解析
147+
if (str.substr(0, 2) !== 's:') {
148+
return str;
149+
}
150+
151+
// 校验签名的值是否合法,如合法,返回true,否则,返回false
152+
var val = unsign(str.slice(2), secret);
153+
154+
if (val !== false) {
155+
return val;
156+
}
157+
158+
return false;
159+
}
160+
```
161+
162+
判断、提取cookie原始值比较简单。只是是`unsign`方法名比较有迷惑性。
163+
164+
一般只会对签名进行合法校验,并没有所谓的反签名。
165+
166+
`unsign`方法的代码如下。首先,从传入的cookie值中,分别提取出原始值A1、签名值B1。用同样的秘钥对A1进行签名,得到A2。根据A2、B1是否相等,判断签名是否合法。
167+
168+
```javascript
169+
exports.unsign = function(val, secret){
170+
var str = val.slice(0, val.lastIndexOf('.'))
171+
, mac = exports.sign(str, secret);
172+
173+
return sha1(mac) == sha1(val) ? str : false;
174+
};
175+
```
176+
177+
## cookie签名的作用
178+
179+
主要是出于安全考虑,防止cookie被篡改,增强安全性。
180+
181+
举个小例子来看下cookie签名是如何实现防篡改的。
182+
183+
基于前面的例子展开。假设网站通过`nick`这个cookie来区分当前登录的用户是谁。在前面例子中,登录用户的cookie中,nick对应的值如下:(decode后的)
184+
185+
```
186+
s:chyingp.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0
187+
```
188+
189+
此时,有人试图修改这个cookie值,来达到伪造身份的目的。比如修改成`xiaoming`
190+
191+
```
192+
s:xiaoming.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0
193+
```
194+
195+
当网站收到请求,对签名cookie进行解析,发现签名验证不通过。由此可判断,cookie是伪造的。
196+
197+
```
198+
hmac("xiaoming", "secret") !== "uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0"
199+
```
200+
201+
## 签名就能够确保安全吗
202+
203+
当然不是。
204+
205+
上个小节的例子,仅通过`nick`这个cookie的值来判断登录的是哪个用户,这是一个非常糟糕的设计。虽然在秘钥未知的情况下,很难伪造签名cookie的,但原始值相同的情况下,签名也是相同的。这种情况下,其实是很容易伪造的。
206+
207+
另外,开源组件的算法是公开的,因此秘钥的安全性就成了关键,要确保秘钥不泄露。
208+
209+
还有很多,这里不展开。
210+
211+
## 小结
212+
213+
本文主要对`Express + cookie-parser`的签名和解析机制进行相对深入的介绍。不少类似的总结文章中,把cookie的签名说成了加密,这是一个常见的错误,读者朋友可以注意一下。
214+
215+
签名部分的介绍,稍微涉及一些简单的安全知识,对这块不熟悉的同学可以留言交流。为讲解方便,部分段落、用词可能不够严谨。如有错漏,敬请指出。
216+
217+
## 相关链接
218+
219+
https://github.com/expressjs/cookie-parser

0 commit comments

Comments
 (0)