PS:已经有不少文章写过这些东西了,我写的目的是为了本身的学习。所学只是为了更好地了解用户登陆鉴权问题。
咱们都知道HTTP是一个无状态的协议javascript
什么是无状态?html
用http协议进行两台计算机交互时,不管是服务器仍是浏览器端,http协议只负责规定传输格式,你怎么传输,我怎么接受怎么返回。它并无记录你上次访问的内容,你上次传递的参数是什么,它无论的。前端
回到咱们要解决的问题,就是用户登陆到了一个网站(index.html),而后点击主页上的某一个超连接跳转到其余页面(another.html),这个时候你在another.html页面就没有了登录状态。这样意味着咱们每次跳转一个页面都要进行一次登录操做,这是极其不合理的。java
为了保证登陆信息以及状态信息可以传递下去,就引入了其余机制git
(1)cookie是实际存在的,存在于客户端,用户可见可修改,不安全。github
(2)cookie在一个域名下是全局的,只要设置path为/,便可从该域名下的任意页面读取cookie中的信息。web
(3)为了安全,HttpOnly设置为true,这样能够必定程度上的预防XSS(跨站脚本攻击)redis
(4)浏览器禁用cookie以后,这种状况下会使用url重写的技术来进行会话跟踪,即在url后面加上sid=xxx参数。算法
大多数应用都是基于cookie来实现session跟踪的。mongodb
session是一种机制,并不实际存在,它由服务器负责管理。关闭浏览器以后,session就会丢失。
具体的过程以下:
(1)客户端第一次发送请求到服务器,服务器生成一个惟一的sessionId,一个sessionId对应一个用户,该sessionId能够存放在Redis或者mongodb中,具体存放在哪里看我的选择
(2)后端将该sessionId放到响应头的Set-Cookie字段,返给前端。
(3)前端记下该sessionId并放到cookie字段,以后每次客户端请求服务器时都会在请求头带上cookie字段,服务端根据sessionId来获取具体信息(好比TTL,过时时间,用户id)
首先固然是引入包了啊,这里选择了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,全称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。
注意:
具体的过程以下:
(1)客户端第一次发送请求到服务端,服务器验证用户信息
(2)服务端生成一个token发送给客户端
(3)客户端保存token,以后每次请求时带上这个token
(4)服务端验证token,返回数据。
与session的过程是相似的,可是缺乏了将jwt保存到服务端,这样便于扩展,不会由于登陆到不一样的服务器致使session没法共享。
第一步固然仍是下包。。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中包含的信息对象