前面提到了通过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);
|
输出结果:
这是用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); })
|
输出结果:
简单实现JWT
思路:
- 我们登录到服务器http://localhost:9000会看到一个表单页面
- 在表单页面提交我们的简单信息(这里只设置名字和年龄),发单会把数据发送给服务器,服务器将数据保存在模拟的session中
- 服务器会给这个数据分配一个session_id,并对这个id加密,作为token返回给浏览器
- 浏览器再次访问服务器(携带token)
- 服务器根据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>
|
服务器端代码:
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 = {};
let server = http.createServer((req, res) => { if(req.url === '/') { let page = fs.readFileSync('./test.html'); res.end(page); } else if (req.url.startsWith('/login')) { let obj = url.parse(req.url, true).query; let cookieValue = "cookie_" + (i++); let TOKEN = jwt.sign({cookie: cookieValue}, pri_key, {algorithm: 'RS256'}); global.mySession[TOKEN] = obj; res.setHeader("set-cookie", "cookie="+TOKEN); res.end("Login succeed"); } else if(req.url.startsWith('/show')) { let myCookies = req.headers.cookie; let cookieValue = myCookies.split("cookie=")[1]; res.end(JSON.stringify(global.mySession[cookieValue])); }
})
server.listen(9000, () => { console.log("server running at 9000"); })
|
我们在浏览器端提交数据后:
浏览器提示成功:
cookie中多了一条数据:
此时我们再登录http://localhost:9000/show,服务器就会把我们的数据返回来: