2

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)

image

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}}

redis

当你退出时,清空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发送给客户端

token

(3)客户端保存token,之后每次请求时带上这个token

image

(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中包含的信息对象

luckyziv
1.2k 声望52 粉丝

摸索中前进!!