专栏首页多云转晴JWT 与 Token 介绍

JWT 与 Token 介绍

JWT Token 介绍

session + cookie 的为了记录客户端状态,也可以说是为了 鉴权,也就是鉴别客户端的权力。比如一个没有登录的用户不能发表评论,而一个普通用户不能观看 VIP 视频,只有开通了才可以观看。这些客户端不同的“权力”就可以通过 session + cookie 的方式进行判断。

而今天要介绍的是另一种可以“鉴权”的技术,它就是 token,token 与 session 无关,是另一种方案。

回顾一下 session, session 好比超市里的保险柜,当你进入超市时,如果手里拿着包或者别的东西,就可以让保险柜吐出一个条形码,然后自动打开一个柜子,把包放里面。当买完东西取包的时候可以使用手里的条形码把柜子打开。手里拿的条形码就好比 cookie(当然,sid 不仅可以使用 cookie 存储,使用什么存储看前端,一般都是使用 cookie),你有正确的 cookie 可以从服务器中取出数据,如果 cookie 丢失或者篡改就表示凭据不正确。超市里的小纸条也是,如果纸条丢了就需要联系商店人员了,而且每个小纸条都是独一无二的,不然你用自己的小纸条可以打开别的柜子那不乱套了吗,cookie 也是不能重复的。

通过上面的超时条形码了解到,条形码是通过柜子产生的,cookie sid 也是通过服务端生成的,发给前端。前端想要从后端取东西都要带上这个凭据,因为后端也存放着这个凭据(sid),通过凭据给你返回数据。因此后端就需要存储这些凭据,最原始的做法就是利用数据结构 —— 哈希表存储起来,这类似于 js 当中的对象(或者 map):

var session = {
    "cbdjckdak1$_#": {
        // 一个用户的 session 数据
    },
    "21693&*&fshadj": {
        // 另一个用户的 session 数据
    }
}

var sid = req.cookie;       // 拿到  sid 凭据
var session = session[sid];     // 获取用户信息

将原始数据存入一个变量中是很耗费内存的,于是可以将 session 放入 Redis 数据库中。

这就是 session 的原理,使用 session 有个要解决的难题,就是做负载均衡时,session 的管理。在学习 Node 时,我们开的服务器就一个,也就是说所有的请求都是由一个服务支撑,如果用户量过大就应接不暇了,可能会导致服务器挂掉,于是需要集群,所谓集群通俗一点说就是将一个服务器“复制”成多个(之前就一个,现在有多个一起运行),我们的电脑内存一般都很大、CPU 核心一般有好几个,因此我们可以使用集群充分利用系统资源,当一个服务挂掉之后,其他的服务会接替它的工作继续运行。

那么问题就来了,一个 sid 在一个服务器上生成,然后发给前端,前端再次发起请求时,这个 sid 可能会被别的服务器收到,因为集群了之后每个服务器的响应是不确定的(负载均衡,多个服务器分摊压力),前端这次发送的请求由当前的服务器处理,下次再发请求时可能就是别的服务器处理了。因此这是个问题,另一个服务器中可能并没有该客户端的 session 信息,因此有需要做额外的处理,这就有些麻烦了。

“复制”就是开多个进程,进程是计算机中的内容,进程之间是不能通信的(主进程与子进程可以),如果你打开一个 QQ,系统就会产生一个 QQ 程序的进程,如果 QQ 进程与其他程序的进程可以通信那还了得!那岂不是“偷窥”别人家的数据了吗。因此集群之后,多个 Node 服务器之间是相互独立的。

因此,使用 session 后还需要考虑服务器负载均衡问题。

听上去那么麻烦,那为何不想一种办法,后端不存放 session 用户标识了,直接把用户标识存放在客户端。而 token 就是这种方式,在 token 中存放着用户的唯一标识,当用户登录后,服务端就生成一个 token 发给前端,前端把 token 存放在客户端,而后端不再存放用户信息(session),当客户端发送请求时就把 token 带上,服务端解析出 token 信息后就可以通过信息来到数据库找到该用户的数据,然后发过去。session 一般在用户访问站点时就发出 sid 了,而 token 往往是用户登录之后才会发放一个 token 凭据

token

JWT

那么如何只通过 token 就可以辨别出是哪个用户呢?在用户注册网站后,数据会保存在数据库中,数据库会自动生成一个 _id,这个 id 是唯一的,可以标识用户身份(不用手机号、QQ 邮箱这种数据作为标识是因为这是用户隐私),用户 id 是一连串的字母+数字促成,长度基本固定,容易被模仿,说 cookie 时也说到,为什么不用 QQ 邮箱作为 sid,而是使用随机字符串作为 sid,这是因为如果我随便在自己的客户端打个字母+数字(或者邮箱)可能就会定位出一个用户,直接登录到他的邮箱。考虑到这些就需要让标识复杂,不容易被模仿。

于是 jwt 应运而生,jwt 全称是 JSON Web Token,即:使用 JSON 生成 Token,官网:JWT[1]jwt 可以把一个 json 数据变成一个字符串,比如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

原来的数据可能是这样的:

{
    "id": user._id
}

加密字符串分为三个部分,分别是 header(头部,标识,加密方式)、payload(负载,就是用户数据)、secret(密钥),看到上面生成的字符串会发现也分成了三部分,但并不是说最后一部分就是密钥,后两部分是不固定的,改变的负载的内容,后两部分都会发生变化。

说到这里你可能会想,既然 jwt 可以生成,那我可以来到官网模仿生成一个。如果数据库中真有一个你猜的用户 id,那也很难很难被验证通过,这是因为后端如果要解出字符串背后的用户信息需要一个密钥(钥匙),通过密钥才能把 token 中的数据还原。因此如果你不知道密钥是什么,就模仿不出一个有效的 token,而密钥是保存在后端的。

如果你的 token 被偷了(一般不会被偷,你手机在你那呢,谁会去偷,如果说是所谓的黑客,那是程序设计有问题,当然,这是不合格的程序员),比如 XSS 攻击等,那没办法,后端会认为是同一个人,当然可以绑定用户 IP,如果异地登陆就做一些处理。这和 sid 一个道理,sid 如果被偷了也可以实现“免密登录”。sid 或者 token 被偷了之后一般偷盗者并不能知道真实用户的密码,也不能修改密码,因此这需要账号验证。想要应对偷盗行为,前端开发者和后端开发中就需要对安全做功课,比如 XSS、CSRF 等攻击。随着浏览器安全性越来越高,这类攻击也越来越少了,现在有一些安全问题是页面伪造,比如恶意网址模仿出一个 QQ 邮箱登录页面,用户还以为是真实的 QQ 邮箱,输入账号、密码后发现什么也没发生,其实用户名和密码已经通过 Ajax 请求发给了恶意网站后端。因此在登陆一些别人发来的登陆页面时,最好谨慎登录,看看有没有常见的二维码登录方式,或者故意输入错位的账号和密码看看页面反应(如果不报错说明很可能就是一个钓鱼网站),但最好不要去尝试。

说了那么多,这差不多就是 token 的全部内容,token 比起 session 而言操作要简洁的多,不用再存储数据,只要解析 token 即可。也就是用 CPU 的计算时间来换取 session 存储空间。在 Node.js 中使用 jwtToken 也很方便,只需要下载一个 jsonwebtoken 包就可以了:

const express = require("express");
const jwt = require("jsonwebtoken");
const { UserModel } = require("./models");
const SECRET = require("./secret");

const app = express();

app.post("/login",async (req,res) => {
    // ... 从服务端拿到 user
    var data = req.body;
    var { email,pwd } = data;

    // mongoDB 查找用户
    var { _id,password,em } = await UserModel.findOne({em: email});
    // 当然,这里还要判密码对不对,按常规来说,用户密码也要加密和解密,在这不讨论
    if(password === pwd && email === em){
        // 生成 token
        const token = jwt.sign({id:  _id},JWTSECRET);
        res.setHeader("auth-token",token);
        res.status(200).send("");
    }
    res.status(422).send("用户名或密码不正确");
})

解密 token 可以使用 jwt.verify 方法进行解密,两个参数,一个是客户端发来的 token,另一个是后端的密钥:

const SECRET = require("./secret");

app.post("/xxx",(req,res) => {
    // 通常前端将 token 放到 `Authorization` 请求头中
    var token = req.headers["authorization"];
    try {       // 验证不对是会出错
        var data = jwt.verify(token,SECRET);
        if(data.id === _id)    res.status(200).send({status: 1,msg: "OK"});
    } catch (err) {
        res.status(400).send({status: 0,msg: err});
    }
})

token 是无状态的,后端并不需要像存储 session 一样去存储 token,只需要解析即可。token 不需要担心负载均衡带来的不便,而且 token 还方便水平扩展,应用场景广泛。因此 token 的使用也很流行,特别是移动端。

应用

上面我们了解到,使用 token 的方式也可以鉴定用户身份。而除了用于普通的登录鉴权之外,token 还可以用在别的地方,比如授权。上面我们也已经说过,鉴定用户身份就是为了“鉴权”,哪些权力用户是可以拥有的,而哪些权力是不能做的,通过 token 就可以鉴别出来。

在使用有些软件时,如果我们不想注册账号,可以选择 QQ 授权登录、微信授权登录等,这也是利用了 token。QQ 会给第三方发一个 token 作为权限,让第三方网站可以获得一些用户数据(当然,敏感信息一般是不会授权的)。

再比如作为开发者,网上有很多数据开放平台,比如高德开放平台,注册完成后会给你发放一个 key,然后我们就可以使用平台中的 API 接口了,这个 key 就相当于 token,用来标识身份,我们只有有了 key 才能调用 API。这也是为了“鉴权”。

参考资料

[1]JWT: https://jwt.io/

本文分享自微信公众号 - Neptune丶(Neptune_mh_0110),作者:多云转晴丶

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-02-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JavaScript 笔试题(三)

    第三行,首先会对 a.x 进行查找,没有找到就会先赋值 undefined,即:{n: 1, x: undefined}。此时 a 和 b 都指向同一个对象。然...

    多云转晴
  • JavaScript 实现二叉搜索树

    这里实现二叉树的方式是使用 ES6 中的 class 类以及立即执行函数的方式实现:

    多云转晴
  • Charles 使用入门

    Charles是HTTP代理/ HTTP监视器/反向代理,使开发人员可以查看其计算机与Internet之间的所有HTTP和SSL / HTTPS通信。这包括请求...

    多云转晴
  • 【thinkphp】app接口签名+验证签名

    【thinkphp】app接口签名+验证签名 app接口签名+验证签名 比较简单 求各位大牛指教 IndexController.class.php <?php...

    96php.cn
  • 忘记token怎么加入k8s集群

    新版本的k8s,初始化生成的token,只有24小时。超过时间,就得需要重新生成token,为了避免这种情况,直接生成永久的token

    py3study
  • Flask扩展实现HTTP令牌token

    在restful设计中,用户认证模式通常使用json web token,而不会使用传统的HTTP Basic认证(传入账号密码) token认证模式如下:在...

    py3study
  • 聊聊 OAuth 2.0 的 token expire_in 使用

    有同学私信问了这样的问题,访问 pig4cloud 的演示环境 查看登录请求 network 返回报文如下:

    冷冷
  • 聊聊 OAuth 2.0 的 token expire_in 使用

    有同学私信问了这样的问题,访问 pig4cloud 的演示环境 查看登录请求 network 返回报文如下:

    冷冷
  • 安卓编译 Jack server 错误问题解决办法

    问题 最近在编译安卓的时候,遇到Jack Server报错的问题:Failed to authenticate Jack server certificat...

    叉叉敌
  • CDOJ 1330 柱爷与远古法阵【高斯消元,卡精度】

    柱爷与远古法阵 Time Limit: 125/125MS (Java/Others)     Memory Limit: 240000/240000KB (J...

    Angel_Kitty

扫码关注云+社区

领取腾讯云代金券