重学Node.js及其框架(Express, Koa, egg.js) 之 token的使用

461 阅读3分钟

总结自 Coderwhy的nodejs课程

查看jwt机制 jwt.io/

认识token

cookie和session的方式有很多的缺点:

  • Cookie会被附加在每个HTTP请求中,所以无形中增加了流量(事实上某些请求是不需要的);

  • Cookie是明文传递的,所以存在安全性的问题;

  • Cookie的大小限制是4KB,对于复杂的需求来说是不够的;

  • 对于浏览器外的其他客户端(比如iOS、Android),必须手动的设置cookie和session;

  • 对于分布式系统和服务器集群中如何可以保证其他系统也可以正确的解析session?

所以,在目前的前后端分离的开发过程中,使用token来进行身份验证的是最多的情况:

  • token可以翻译为令牌;

  • 也就是在验证了用户账号和密码正确的情况,给用户颁发一个令牌;这个令牌作为后续用户访问一些接口或者资源的凭证;

  • 我们可以根据这个凭证来判断用户是否有权限来访问;

所以token的使用应该分成两个重要的步骤:

  • 生成token:登录的时候,颁发token;

  • 验证token:访问某些资源或者接口时,验证token;

通过jwt来实现token

JWT生成的Token由三部分组成:

  • header

    • alg:采用的加密算法,默认是 HMAC SHA256(HS256),采用同一个密钥进行加密和解密;
    • typ:JWT,固定值,通常都写成JWT即可;会通过base64Url算法进行编码
  • payload

    • 携带的数据,比如我们可以将用户的id和name放到payload中;
    • 默认也会携带iat(issued at),令牌的签发时间;
    • 我们也可以设置过期时间:exp(expiration time);
    • 会通过base64Url算法进行编码
  • signature

    • 设置一个secretKey,通过将前两个的结果合并后进行HMACSHA256(默认编码)的算法;

    • HMACSHA256(base64Url(header)+.+base64Url(payload), secretKey);

    • 如果secretKey暴露是一件非常危险的事情,因为之后就可以模拟颁发token,也可以解密token; 注意:前两个字段是通过base64编码的,可以反向解码。但是最后一个字段一般不行。

image.png

token生成的加密方式

对称加密

显而易见,就是对token加密和解密都使用同一个秘钥。即HS256加密算法

非对称加密

我们对于token的加密和解密使用两个秘钥

  • 私钥(private key):用于发布令牌
  • 公钥(public key):用于验证令牌; 如何来生成公钥和私钥呢?
  • mac电脑直接在终端执行下面命令即可。
  • windows电脑,需要在git bash中执行下面命令。
    openssl
    genrsa -out private.key 1024
    rsa -in private.key -pubout -out public.key

在开发中使用token

在真实开发中,我们可以直接使用一个库来完成: jsonwebtoken;


    const jwt = require("jsonwebtoken");
    async login (ctx, next) {
        // 登陆成功后取出user
        const { id, username } = ctx.user;
        // 生成token。通过私钥生成token
        const token = jwt.sign({ id, username }, PRIVATE_KEY, {
          // 设置过期时间
          expiresIn: 60 * 60 * 24,
          // 设置编码格式
          algorithm: 'RS256'
        })
        ctx.body = {
          id,
          username,
          token
        }
      }

解析token

// 验证授权,解密
    async function verifyAuth (ctx, next) {
      const authorization = ctx.request.headers.authorization;
      const token = authorization && authorization.replace("Bearer ", "");
        // 通过公钥解析token
        const user = jwt.verify(token, PUBLIC_KEY, {
          algorithms: ["RS256"]
        })
        // 保存user数据,以后有用
        ctx.user = user
        await next()
    }