cookie✘session✘jwt
写在前面
PS:已经有很多文章写过这些东西了,我写的目的是为了自己的学习。所学只是为了更好地了解用户登录鉴权问题。
我们都知道HTTP是一个无状态的协议
什么是无状态?
用http协议进行两台计算机交互时,无论是服务器还是浏览器端,http协议只负责规定传输格式,你怎么传输,我怎么接受怎么返回。它并没有记录你上次访问的内容,你上次传递的参数是什么,它不管的。
回到我们要解决的问题,就是用户登录到了一个网站(index.html),然后点击主页上的某一个超链接跳转到其他页面(another.html),这个时候你在another.html页面就没有了登陆状态。这样意味着我们每次跳转一个页面都要进行一次登陆操作,这是极其不合理的。
为了保证登录信息以及状态信息能够传递下去,就引入了其他机制
session和cookie
Cookie
(1)cookie是实际存在的,存在于客户端,用户可见可修改,不安全。
(2)cookie在一个域名下是全局的,只要设置path为/,即可从该域名下的任意页面读取cookie中的信息。
(3)为了安全,HttpOnly设置为true,这样可以一定程度上的预防XSS(跨站脚本攻击)
(4)浏览器禁用cookie之后,这种情况下会使用url重写的技术来进行会话跟踪,即在url后面加上sid=xxx参数。
大多数应用都是基于cookie来实现session跟踪的。
Session
session是一种机制,并不实际存在,它由服务器负责管理。关闭浏览器之后,session就会丢失。
具体的过程如下:
(1)客户端第一次发送请求到服务器,服务器生成一个唯一的sessionId,一个sessionId对应一个用户,该sessionId可以存放在Redis或者mongodb中,具体存放在哪里看个人选择
(2)后端将该sessionId放到响应头的Set-Cookie字段,返给前端。
(3)前端记下该sessionId并放到cookie字段,之后每次客户端请求服务器时都会在请求头带上cookie字段,服务端根据sessionId来获取具体信息(比如TTL,过期时间,用户id)
Koa2中使用session
首先当然是引入包了啊,这里选择了koa-session2
`。koa-session2`已经帮你做好了所有,使用起来相当简单,方便快速开发。
const Koa = require("koa")
const app = new Koa()
const session = require("koa-session2")
app.use(session({
stort: new RedisStore(), //存放session的地方,我这里选择放到redis里
key: "SESSION_ID"
}))
new RedisStore()
又是什么呢?其实查看koa-session2的git仓https://github.com/Secbone/koa-session2就能知道
以下来自官方git仓:
const Redis = require("ioredis");
const { Store } = require("koa-session2");
class RedisStore extends Store {
constructor() {
super();
this.redis = new Redis(); // 连接redis
}
async get(sid, ctx) {
let data = await this.redis.get(`SESSION:${sid}`);
return JSON.parse(data);
}
async set(session, { sid = this.getID(24), maxAge = 1000000 } = {}, ctx) {
try {
// Use redis set EX to automatically drop expired sessions
// 设置redis的Ex 以自动丢弃过期的session
await this.redis.set(`SESSION:${sid}`, JSON.stringify(session), 'EX', maxAge / 1000);
} catch (e) {}
return sid;
}
async destroy(sid, ctx) { // 删除redis中的数据
return await this.redis.del(`SESSION:${sid}`);
}
}
module.exports = RedisStore;
接下来就是判断登陆,以及将需要的信息写入session
let sid = ctx.cookies.get("SESSION_ID") // 获得cookie中的sid
ctx.session.myinfo = {a: 1, b: 2} //将一个数据对象放到session中
// 最后的结果就是:
// redis中的键为SESSION:sid
// 值为{myinfo: {a: 1, b: 2}}
当你退出时,清空cookie和session即可
ctx.cookies.set("SESSION_ID", "")
ctx.session = null
JWT
简答认识JWT
让我们来看看重头戏JWT,全称JSONWebToken
,是一种目前较为流行的验证方式。
JWT由三部分组成,第一部分我们称它为头部(header),
//header
{
'typ': 'JWT', // 类型
'alg': 'HS256' // 加密算法
}
// 对其base64 得到了第一个部分
第二部分我们称其为载荷(payload, 类似于飞机上承载的物品)
// payload 存放有效信息的地方 比如userId
{
"id": "1234567890",
"name": "John Doe",
"isMan": true
}
// 对其base64 得到第二个部分
第三部分是签证(signature).
// 将base64后的header和base64后的payload使用.连接组成新的字符串,然后使用header中声明的加密方式对其进行加盐secret组合加密,得到了第三个部分
将三部分用.
连接成一个完整的字符串,得到了最终的jwt。
注意:
- 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
- 保护好secret私钥,该私钥非常重要。
- 如果可以,请使用https协议
具体的过程如下:
(1)客户端第一次发送请求到服务端,服务器验证用户信息
(2)服务端生成一个token发送给客户端
(3)客户端保存token,之后每次请求时带上这个token
(4)服务端验证token,返回数据。
与session的过程是类似的,但是缺少了将jwt保存到服务端,这样便于扩展,不会因为登录到不同的服务器导致session无法共享。
Koa2中使用jwt
第一步当然还是下包。。npm install jsonwebtoken
const jwt = require("jsonwebtoken")
服务端生成token,并返给前端
// 生成token
const token = jwt.sign({role: user.role, id: user._id}, key, {expiresIn: "1 days"}) // 第一个参数为负载的信息,第二个参数为secret,第三个参数我是过期时间
// 返回前端
ctx.body = {
token: token
}
客户端之后发起请求,应该带上token字段,将其放在authorization请求头字段或者以query的方式。
if(ctx.header.authorization && ctx.headers.authorization.split(' ')[0] === "Bearer") {
token = ctx.header.authorization.split(' ')[1]
} else if(ctx.query && ctx.query.token) {
token = ctx.query.token
}
然后服务端验证token,进行相应的处理返回数据。
// 解密token 一般使用jwt.verify不适用jwt.decode
let decoded = jwt.verify(token, key) // 得到token中包含的信息对象
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。