express+jwt

转自: https://www.cnblogs.com/wjlbk/p/12633302.html

Express + JWT用户认证最轻实践

最近给自己列了一个list,Ummm...列来列去大概是下面这个样子:

  • React SSR服务端渲染
  • jwt用户认证
  • Vue全家桶
  • 微信小程序开发
  • ... 等等

好吧,谁让自己菜呢,没什么好抱怨的,一个一个来吧。正好最近看了一些token做身份认证的文章,发现其中大部分都是说token登录怎么怎么好,反正没有几个认认真真的实现的。。。正好,秉着我是小白我怕谁的原则,继续分享一下express + jwt的填坑经历。为什么题目起名是最轻实践呢?因为确实看完这个你可以大概理解token登录的好处以及如何简单的实现一个前后端通过token进行认证的小系统。这个demo是在我第一篇文章那个脚手架上跑起来的,感兴趣的还可以回顾一下----->express-react-scaffold。具体实现就是下面这个样子:

  • 不用token验证的页面正常浏览
  • 需要验证的页面进行token验证
  • 没有token信息或token信息过期,提示用户重新登录,跳转到登录页面
  • 登录成功之后每次请求携带token信息[图片上传失败...(image-dfb171-1615444929797)]

这篇文章包括

  • 为什么要用token做身份验证(另一种模式是session)
  • 前端http请求拦截器的设置
  • 后端express + jsonwebtoken实现基于token的用户身份验证

token是个啥子东西

身份认证的两种方式

在前后端分离的系统中,身份认证是十分重要的,目前常用的两种身份认证方式如下:

  • 基于cookie
    基于cookie的服务端认证,就是我们所熟知session,在服务端生成用户相关的 session 数据,而发给客户端 sesssion_id 存放到 cookie 中,这样用客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户认证。
  • 基于Token令牌
    基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用户验证后,服务端生成一个 token(hash 或 encrypt)发给客户端,客户端可以放到 cookie 或 localStorage(sessionStorage) 中,每次请求时在 Header 中带上 token ,服务端收到 token 通过验证后即可确认用户身份。

token认证的好处

  • 体积小(一串字符串),因而传输速度快
  • 传输方式多样,可以通过HTTP 头部(推荐)、 URL、POST 参数等方式传输严谨的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持为应用定制化支持跨域验证,多应用于单点登录 充分依赖无状态 API ,契合 RESTful 设计原则(无状态的 HTTP)
  • 用户登录之后,服务器会返回一串 token 并保存在本地也就是客户端,在这之后的对服务器的访问都要带上这串 token,来获得访问服务器相关路由、服务及资源的权限。 易于实现 CDN,将静态资源分布式管理
  • 在传统的 session 验证中,服务端必须保存 session ID,用于与用户传过来的 cookie 验证。而一开始 sessionID 只会保存在一台服务器上,所以只能由一台 server 应答,就算其他服务器有空闲也无法应答,无法充分利用到分布式服务器的优点。 JWT 依赖的是在客户端本地保存验证信息,不需要利用服务器保存的信息来验证,所以任意一台服务器都可以应答,服务器的资源也被较好地利用。
  • 对原生的移动端应用支持较好 原生的移动应用对 cookie 与 session 的支持不够好,而对 token 的方式支持较好。

JWT的组成

JWT的本质实际上就是一个字符串,它有三部分组成头部+载荷+签名。

// Header
{
  "alg": "HS256",//所使用的签名算法
  "typ": "JWT"
}

// Payload
{
  //该JWT的签发者
  "iss": "luffy",
  // 这个JWT是什么时候签发的
  "iat":1441593502,
  //什么时候过期,这是一个时间戳
  "exp": 1441594722,
  // 接收JWT的一方
  "aud":"www.youdao.com",
  // JWT所面向的用户
  "sub":"any@126.com",
  // 上面是JWT标准定义的一些字段,除此之外还可以私人定义一些字段
  "form_user": "fsdfds"
}

// Signature 签名
将上面两个对象进行base64编码之后用.进行连接,然后通过HS256算法进行加密就形成了签名,一般需要加上我们提供的一个密匙,例如secretKey:'name_luffy'
const base64url = require('base64url')

const base64header = base64url(JSON.stringify(header));
const base64payload = base64url(JSON.stringify(payload));
const secretKey = 'name_luffy';
const signature = HS256(`${base64header}.${base64payload}`,secretKey);
// JWT
// 最后就形成了我们所需要的JWT:
const JWT = base64header + "." + base64payload + "." + signature;
// 它长下面这个样子:
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
复制代码

JWT的工作原理

我从官网JWT.io拿下来的图来展示,就是下面这个过程,说的很详细,此外还有一些细节的东西,比如什么形式存储,放在头部哪里,客户端要存储在哪里等,官网都有比较详细的介绍,大家可以去看看。

1.png

(https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxOC81LzI4LzE2M2E1NjllMjRiZmZiOTM?x-oss-process=image/format,png)

前后端如何用这个东西做身份认证

思路

接下来要详细的说如何使用jwt来进行前后端的身份验证了,具体思路如下:

  • 用户登录注册的逻辑不需要身份验证,因为没有用户的身份信息和登录状态;
  • 用户登录之后后端生成token并返给前端,前端拿到token之后将token缓存在本地,可以使localStorage也可以是cookie,以便接下来使用。。
  • 其他内容涉及到前后端交互的都需要前端把认证的token信息放在请求头部传给后端
  • 后端收到请求先校验token,如果token合法(也就是token正确且没过期),则执行next(),否则直接返回401以及对应的message。

token登录的具体实现细节

  • 后端:express-jwt + jsonwebtoken 首先,安装两个包
yarn add express-jwt jsonwebtoken 
复制代码

之后就是在登录环节生成token并且把token返回给前端

// /routes/user.js
if (user !== null) {
    // 用户登录成功过后生成token返给前端
  let token = jwt.sign(tokenObj, secretKey, {
        expiresIn : 60 * 60 * 24 // 授权时效24小时
  });
  res.json({
        success: true,
        message: 'success',
        token: token
  });
} 
复制代码

其次,设置拦截token的中间件,包括token的验证以及错误信息的返回:

// jwt.js,token中间件
const expressJwt = require("express-jwt");
const { secretKey } = require('../constant/constant');
// express-jwt中间件帮我们自动做了token的验证以及错误处理,所以一般情况下我们按照格式书写就没问题,其中unless放的就是你想要不检验token的api。
const jwtAuth = expressJwt({secret: secretKey}).unless({path: ["/api/user/login", "/api/user/register"]}); 

module.exports = jwtAuth;
复制代码
// constant.js
// 设置了密码盐值以及token的secretKey
const crypto = require('crypto');

module.exports = {
  MD5_SUFFIX: 'luffyZhou我是一个固定长度的盐值',
  md5: (pwd) => {
    let md5 = crypto.createHash('md5');
    return md5.update(pwd).digest('hex');
  },
  secretKey: 'luffy_1993711_26_jwttoken'
};
复制代码

最后在路由中间件前面放上jwt中间件

// routes/index.js
// 所有请求过来都会进行身份验证
router.use(jwtAuth);
// 路由中间件
router.use((req, res, next) => {
  // 任何路由信息都会执行这里面的语句
  console.log('this is a api request!');
  // 把它交给下一个中间件,注意中间件的注册顺序是按序执行
  next();
});
复制代码

后端逻辑部分全部完成,下面是前端的实现部分。

  • 前端: axios拦截器 + localStorage存储token 前端主要做的就是两件事:

第一、把登陆成功之后返回的token存在客户端,可以使用localStorage也可以使用cookie,我看官方推荐使用localStorage,我这边也就用localStorage吧。 第二、每次请求把token放到header头部Authorization字段。

// axios拦截器
// 拦截请求,给所有的请求都带上token
axios.interceptors.request.use(request => {
  const luffy_jwt_token = window.localStorage.getItem('luffy_jwt_token');
  if (luffy_jwt_token) {
    // 此处有坑,下方记录
    request.headers['Authorization'] =`Bearer ${luffy_jwt_token}`;
  }
  return request;
});

// 拦截响应,遇到token不合法则报错
axios.interceptors.response.use(
  response => {
    if (response.data.token) {
      console.log('token:', response.data.token);
      window.localStorage.setItem('luffy_jwt_token', response.data.token);
    }
    return response;
  },
  error => {
    const errRes = error.response;
    if (errRes.status === 401) {
      window.localStorage.removeItem('luffy_jwt_token');
      swal('Auth Error!', `${errRes.data.error.message}, please login!`, 'error')
      .then(() => {
        history.push('/login');
      });
    }
    return Promise.reject(error.message);   // 返回接口返回的错误信息
  });
复制代码

此处有坑,在此记录request.headers['Authorization']必须通过此种形式设置Authorization,否则后端即使收到字段也会出现问题,返回401,request.headers.Authorization或request.headers.authorization可以设置成功,浏览器查看也没有任何问题,但是在后端会报401并且后端一律只能拿到小写的,也就是res.headers.authorization,后端用大写获取会报undefined.


2.png
2.png

可以看到,登录成功后,token被存放在localStorage里并且每一次请求都会将token放在头部Authorization字段内。如果我们把token从localStorage清除,再次访问就会报错。

3.png
4.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容