资讯专栏INFORMATION COLUMN

jwt前后端整合方案

nevermind / 348人阅读

摘要:到这里,基于的前后端分离实现方案就搞定啦四关于的一些思考实际上,在使用的过程中有一个比较致命的缺点,就是一旦签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。结语以上是关于基于的前后端分离实现方案的总结和思考。

一、jwt是什么

JWT全称, JSON Web Token,是一个以JSON为基准的标准规范。

举例:服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样

{
  "姓名": "brook",
  "角色": "前端攻城狮",
  "帅气指数": "5颗星"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

我们先看看jwt的真实面目

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwibmFtZSI6ImFkbWluIiwidXNlcm5hbWUiOiJhZG1pbiIsInBvc2l0aW9uIjoiIiwicGhvbmUiOm51bGwsImVtYWlsIjpudWxsLCJyb2xlIjpbImFkbWluIl0sImF2YXRhciI6Imh0dHA6Ly9pbWcuZG9uZ3FpdWRpLmNvbS91cGxvYWRzL2F2YXRhci8yMDE1LzA3LzI1L1FNMzg3bmg3QXNfdGh1bWJfMTQzNzc5MDY3MjMxOC5qcGciLCJpbnRyb2R1Y3Rpb24iOiIiLCJjcmVhdGVfdGltZSI6IjIwMTctMTEtMDJUMTg6MTU6NDguMDAwWiIsInVwZGF0ZV90aW1lIjoiMjAxNy0xMS0yNlQwNjozMzoxNy4wMDBaIiwiaWF0IjoxNTM5MjQ0NjQ1fQ.cRg7ZAQ-1ZBiJUPDx6naQupUMK2BLHmIusMQZrnqVpG

它是一个很长的字符串,中间用点(.)分隔成三个部分。三个部分依次为

    Header(头部)
    Payload(负载)
    Signature(签名)

即header.payload.sign。

Header 部分是一个 JSON 对象,描述 JWT 的元数据。
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
Signature 部分是对前两部分的签名,防止数据篡改。

关于这三部分的详解,可以具体参考阮一峰老师的文章:http://www.ruanyifeng.com/blo...

在使用jwt的时候建议放在 HTTP 请求的头信息Authorization字段里面,如下

    Authorization: Bearer 
二、jwt的好处

前后端分离:使用JWT作为接口鉴权不需要前端代码发布到后端指定目录下,可以完全跨域,前端项目可以多带带部署

减轻服务端内存负担:比起使用session来保存cookie,JWT自身包含了所有信息,通过解密即可验证(当然啦,这个通过吧session存在redis来避免)

安全性:防止CSRF攻击

移动端:对于无法使用cookie的一些移动端,JWT能够正常使用

部署:服务器不需要保存session数据,无状态,容易扩展

PS:为什么不写为什么使用jwt呢,因为它其实还是存在不少缺点的,需要根据使用业务场景确定,不是所有的场景都适合使用jwt,甚至网上有些帖子都是在评论jwt比较鸡肋的。具体可以看分析
https://juejin.im/entry/59748...

三、jwt怎么使用

直接上图,流程如下

我们以Eggjs项目为例,使用koa-jwt这个库
https://github.com/koajs/jwt

后端(以Eggjs项目为例)
1、在config.default.js 中以中间件的方式使用koa-jwt
  config.middleware = ["compress", "errorHandler","jwt"];
  // 加上配置
  config.jwt = {
    match: "/api",
    secret: "abiao",
    unless: ["/api/user/login"],
  };

match指egg路由匹配到相应前缀,则会使用当前的中间件。可以使用正则表达式去匹配,推荐api前缀定为api。

secret指jwt的加密密钥。

unless指指定的路由不经过此中间件,一般为login接口

PS:egg中使用插件有全局模式和中间件模式。全局模式应该使用egg的插件,中间件模式可以使用第三方koa的插件

2、在middlewares文件夹目录下增加jwt中间件

jwt.js

    const jwt = require("koa-jwt");
    module.exports = (options, app) => {
      let jwtMiddlerware = jwt(
        {
          secret: options.secret,
        }
      );
      if (options.unless) {
        jwtMiddlerware = jwtMiddlerware.unless({path: options.unless})
      }
      return jwtMiddlerware
    };

egg会自动往middleware的中间件里注入配置。在中间件里就可以使用koa-jwt对我们的接口进行保护

3、在登录接口里做好jwt发放,剔除密码等敏感信息
    * login() {
      const params = this.ctx.request.body;
      const rule = {
        username: "string",
        password: "string"
      };
      this.ctx.validate(rule, params);
      // 调用 service 进行业务处理
      const res = yield this.service.user.login(params);
      // 获取jwt的配置
      let {jwt:jwtConf} = app.config;
      // 使用密钥对用户数据进行加密,生成jwt
      let token = jwt.sign(res,jwtConf.secret);
      res.token = token;
      this.ctx.body = this.ctx.helper.success(res);
    }
前端
1、登录时使用用户名和密码请求登录接口,拿到接口返回的jwt,把jwt存储到localStorage里
    LoginByUsername({ commit }, userInfo) {
      const username = userInfo.username.trim()
      return new Promise((resolve, reject) => {
        loginByUsername(username, userInfo.password).then(response => {
          const data = response.data
          // 把jwt存储到localStorage里
          LocalStorage.setItem("token", data.payload.token)
          resolve(data)
        }).catch(error => {
          reject(error)
        })
      })
    }

PS:网上关于jwt应该存储到哪里有一篇分析,推荐是存到cookie里,因为可以避免XSS攻击,链接如下:
https://blog.csdn.net/loveyou...

经过实践,假如在服务端把token写入cookie,并设置为httpOnly,本地前端是获取不到cookie里的token的,也就没办法在header里带上token给后端校验,不可行;所以token还是需要让前端自己存储,而前端把token存储在cookie是没办法设置httpOnly的,所以规避不了XSS攻击。不管是在放localStorage和cookie里都会遇到XSS的问题,这个只能通过对用户输入进行转码来防范;而放在localStorage里会比放在cookie里好,因为每次请求不会在cookie里又带上token,减少了请求体的大小。

综上所述,我认为token应该让前端存储在localStorage里,同时做好XSS防范。

2、前端在后续请求的时候在header里带上jwt

推荐的做法是使用请求拦截器,推荐使用axios

    import axios from "axios"
    
    // 创建axios实例
    const service = axios.create({
      baseURL: "/",
      timeout: 5000
    })
    
    // request拦截器
    service.interceptors.request.use(config => {
      if (LocalStorage.getItem("token")) {
        config.headers["authorization"] = "Bearer " + LocalStorage.getItem("token")
      }
      return config
    }, error => {
      Promise.reject(error)
    })

写完基本流程之后我们带上jwt请求一个接口看看效果


返回结果正常。同时,我们也验证一下没有token的情况下。我们手动清除了cookie再请求一次接口

接口会返回unauthorizeError,这个是我们期待的返回结果。当然啦,我们也可以在后端catch这个错误,返回更加友好的信息,例如401,让前端提示会话过期并自动跳转到登录页。我们简单演示这一步就先跳过了。

到这里,基于jwt的前后端分离实现方案就搞定啦!

四、关于jwt的一些思考

实际上,jwt在使用的过程中有一个比较致命的缺点,就是一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。这对于要临时禁止某个用户的操作,修改某个用户权限并马上生效的业务场景,是满足不了的,而对于做得比较完善的业务系统来讲都会有类似的需求,所以是否使用jwt,还需要谨慎评估。

JWT 的最佳用途是「一次性授权 Token」,这种场景下的 Token 的特性如下:
有效期短,只希望被使用一次。
例如分享一个文件给朋友,在指定1小时打开有效。

结语

以上是关于基于jwt的前后端分离实现方案的总结和思考。此外分享一个accesstoken的方案,可以作为jwt的替代方案,详情可以查看loopback框架的Authorization,可以满足大部分的业务场景。
https://loopback.io/doc/en/lb...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103958.html

相关文章

  • SpringBoot+SpringSecurity+jwt整合及初体验

    摘要:进行下一项配置,为了区分必须加入。另起一行,以示尊重。这行代码主要是用于验证,后面再说。然后跑下接口,发现没问题,正常打印,说明主体也在上下文中了。说明这会上下文环境中我们主体不存在。所说以,主体数据生命周期是一次请求。 showImg(https://segmentfault.com/img/bVbtoG1?w=1600&h=900); 原来一直使用shiro做安全框架,配置起来相当...

    dackel 评论0 收藏0
  • api权限管理系统与前后分离实践

    摘要:自己在前后端分离上的实践要想实现完整的前后端分离,安全这块是绕不开的,这个系统主要功能就是动态管理,这次实践包含两个模块基于搭建的权限管理系统后台编写的前端管理。 自己在前后端分离上的实践 要想实现完整的前后端分离,安全这块是绕不开的,这个系统主要功能就是动态restful api管理,这次实践包含两个模块,基于springBoot + shiro搭建的权限管理系统后台bootshir...

    bawn 评论0 收藏0
  • api权限管理系统与前后分离实践

    摘要:自己在前后端分离上的实践要想实现完整的前后端分离,安全这块是绕不开的,这个系统主要功能就是动态管理,这次实践包含两个模块基于搭建的权限管理系统后台编写的前端管理。 自己在前后端分离上的实践 要想实现完整的前后端分离,安全这块是绕不开的,这个系统主要功能就是动态restful api管理,这次实践包含两个模块,基于springBoot + shiro搭建的权限管理系统后台bootshir...

    tianlai 评论0 收藏0

发表评论

0条评论

nevermind

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<