文章目录加载中

身份认证-Token机制

# 为什么需要 Token?

这也是我刚接触 token 时候的疑惑,因为 session 对比 cookie 来说,解决了很多问题,所以感觉上 session 已经很完美了。

但对于 session 来说,服务器是有状态的。这个事情就很麻烦,尤其是在分布式部署服务的时候,需要共享服务器之间的状态。总不能让用户不停重复登陆吧?虽然专门准备一个服务器用来处理状态是可行的,但是能不能让服务器变成无状态的,还不能像单纯 cookie 那么蹩脚?

token 就解决了这个问题。它将状态保存在客户端,并且借助加密算法进行验证保证安全性。

# 整体流程

如上图所示,整体流程总结如下:

  • 用户尝试登陆
  • 登陆成功后,后端依靠加密算法,将凭证生成 token,返回给客户端
  • 客户端保存 token,每次发送请求时,携带 token
  • 后端再接收到带有 token 的请求时,验证 token 的有效性

在整个流程中,比较重要的是:生成 token、验证 token 的过程。这里设计一种简单的技术实现

  • 生成:token 的组成为:${user}.${HS256(user, secret)}。其中,secret 是加密需要的密钥,保存在服务端,不能泄漏。HS256 是加密算法,使用 RS256、HS512 也可以。
  • 验证:将请求中携带的 token 按照.分开,得到payloadsig。用服务器密钥对payload进行加密,将加密结果和sig比较,如果相同,那么通过验证。

值得一提的是,这里无需对sig进行解密。

# 代码实现

借助crypto实现 HS256 算法加密:

/**
 * @param {string} content
 * @param {string} key
 * @return {string}
 */
function HS256(content, key = "jnajdnf9328u4") {
    const hmac = crypto.createHmac("sha256", key);
    hmac.update(content);
    return hmac.digest("hex");
}

用户登陆成功后,将用户名作为payload,生成对应的sig,拼接为 token,返回给客户端:

router.get("/login", async (ctx, next) => {
    const { user, pwd } = querystring.parse(ctx.request.search.slice(1));
    // mock数据,模拟一下登陆过程
    if (user === "test" && pwd === "123456") {
        ctx.response.body = {
            token: `${user}.${HS256(user)}`
        };
    } else {
        ctx.response.body = "登陆失败";
        ctx.response.status = 401;
    }
});

客户端再请求需要用户身份的 api 的时候,应该携带 token,服务端对 token 合法性进行检验:

router.get("/userInfo", async (ctx, next) => {
    const { token } = querystring.parse(ctx.request.search.slice(1));
    if (typeof token !== "string") {
        ctx.response.body = "请携带token";
        ctx.response.status = 401;
        return;
    }
    const [payload, sig] = token.split(".");
    if (!payload || !sig) {
        ctx.response.body = "token格式不合法";
        ctx.response.status = 401;
        return;
    }
    if (HS256(payload) !== sig) {
        ctx.response.body = "签名不合法";
        ctx.response.status = 401;
        return;
    }
    ctx.response.body = "用户信息是巴拉巴拉";
});

请求成功后,服务端返回数据,下图所示:

# 总结:token 真香

token 的优点多多:

  • 服务器变成无状态了,实现分布式 web 应用授权
  • 可以进行跨域授权,不再局限父子域名
  • token 设计绝对了它本身可以携带更多不敏感数据,例如最常用的 JWT
  • 安全性更高,密钥保存在服务器。若密钥被窃取,可以统一重新下发密钥。

当然,token 增加了服务器压力(毕竟要加密)。但还是真香

本文来自心谭博客:xin-tan.com,经常更新web和算法的文章笔记,前往github查看目录归纳:github.com/dongyuanxin/blog