node.js学习笔记: jsonwebtoken中间件加密token

前面提到了通过cookie-parser中间件对cookie签名,从而避免cookie被修改。但实际上签名并不能保证cookie不被辨识出来,那么要增加安全性,还有一个方法是生成token。

cookie存在安全性问题

利用cookie来干坏事的典型就是CSRF(Cross-Site Request Forgery)跨域站点伪造。

当我们不小心点击了黑客制作的陷阱页面,可能它看起来和正常网页一模一样,其中有一个表单,我们在不知情的情况下点击了提交,于是浏览器把包含有cookie的请求发送到了黑客的服务器上,黑客获取到了我们的cookie数据,于是他们就可以利用我们的数据来做一些损害我们利益的事情。

比如我们想要登录银行的页面www.yinhang.com查看余额,结果不小心点击了黑客制作的www.yinghang.com,并且点击了登录,于是黑客就获取到了我们登录www.yinhang.com所需要的cookie,此时他们就可以用这个cookie登录我们的银行账户,并且把我们的余额转给自己。

Token令牌

token翻译过来就是“令牌”的意思,它的机制和cookie类似,由服务端生成一串字符串,作为客户端进行请求的一个标识。

这篇文章能够帮助我们理解token:你应该知道这十件关于token的事

token在常用于移动端原生应用中,因为没有浏览器,就更不要谈cookie了。

JWT

JSON Web Token是一种解决跨域身份认证的方案。

这篇文章能够帮我们理解JWT:JSON Web Token 入门教程

我们可以通过jsonwebtoken中间件来尝试创建token:

1
2
3
4
5
const jwt = require("jsonwebtoken");
let token = jwt.sign({foo: 'bar'}, 'shhhhh');
console.log("token: ", token);
let decoded = jwt.verify(token, 'shhhhh');
console.log('decoded: ', decoded.foo);

输出结果:

image-20180813144925195

这是用HMAC SHA256方法进行加密的。

我们还可以用其他方法来加密,比如RSA SHA256,我们可以从github上找到测试用的公钥和私钥。

1
2
3
4
5
6
7
8
9
10
11
12
const jwt = require("jsonwebtoken");
const fs = require("fs");

let pri_key = fs.readFileSync("./private.key");
let token = jwt.sign({foo:'bar'}, pri_key, {algorithm: 'RS256'});
console.log("token: ", token);

let pub_key = fs.readFileSync("./public.key");
jwt.verify(token, pub_key, (err, decoded) => {
if(err) throw err;
console.log("decoded: ", decoded.foo);
})

输出结果:

image-20180813150238225

简单实现JWT

思路:

  1. 我们登录到服务器http://localhost:9000会看到一个表单页面
  2. 在表单页面提交我们的简单信息(这里只设置名字和年龄),发单会把数据发送给服务器,服务器将数据保存在模拟的session中
  3. 服务器会给这个数据分配一个session_id,并对这个id加密,作为token返回给浏览器
  4. 浏览器再次访问服务器(携带token)
  5. 服务器根据token找到session中对应的数据,返回给浏览器

我们先搭建一个简单的页面:

1
2
3
4
5
<form action="http://localhost:9000/login" method="get" accept-charset="utf-8">
<label>姓名: <input type="text" name="name" value=""></label><br>
<label>年龄: <input type="text" name="age" value=""></label>
<input type="submit" name="">
</form>

image-20180813162444663

服务器端代码:

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
const http = require("http");
const jwt = require("jsonwebtoken");
const fs = require("fs");
const url = require("url");

let pri_key = fs.readFileSync('./private.key'); //秘钥

let i = 1;
global.mySession = {}; //模拟的session数据

let server = http.createServer((req, res) => {
if(req.url === '/') { //如果用户直接访问,就返回登录页面
let page = fs.readFileSync('./test.html');
res.end(page);
} else if (req.url.startsWith('/login')) { //如果用户提交数据到/login页面
// 解析用户请求,获取用户的数据
// 打印出来是{name: xxx, age: xx}
let obj = url.parse(req.url, true).query;
// 每个用户一个session_id,这里的形式就是cookie_1, cookie_2...以此类推
let cookieValue = "cookie_" + (i++);
// 加密token
let TOKEN = jwt.sign({cookie: cookieValue}, pri_key, {algorithm: 'RS256'});
// 关联session和token,让token对应用户的数据
global.mySession[TOKEN] = obj;
// 在响应头的cookie中返回数据
res.setHeader("set-cookie", "cookie="+TOKEN);
res.end("Login succeed");
} else if(req.url.startsWith('/show')) { //如果用户想要查看数据
// 获取用户的所有cookies
let myCookies = req.headers.cookie;
// 找到key为cookie的那一条
// 所有的cookies会以一长串字符串的形式发过来,所以我直接在cookie=这部分切开
let cookieValue = myCookies.split("cookie=")[1];
// 在session中找到对应的数据,解析后返回给浏览器
res.end(JSON.stringify(global.mySession[cookieValue]));
}

})

server.listen(9000, () => {
console.log("server running at 9000");
})

我们在浏览器端提交数据后:

image-20180813163254261

浏览器提示成功:

image-20180813163331766

cookie中多了一条数据:

image-20180813163406589

此时我们再登录http://localhost:9000/show,服务器就会把我们的数据返回来:

image-20180813163500214