jwt先後端整合方案

1、jwt是什麼

JWT全稱, JSON Web Token,是一個以JSON爲基準的標準規範。 html

舉例:服務器認證之後,生成一個 JSON 對象,發回給用戶,就像下面這樣前端

{
  "姓名": "brook",
  "角色": "前端攻城獅",
  "帥氣指數": "5顆星"
}

之後,用戶與服務端通訊的時候,都要發回這個 JSON 對象。服務器徹底只靠這個對象認定用戶身份。爲了防止用戶篡改數據,服務器在生成這個對象的時候,會加上簽名(詳見後文)。
將上面的 JSON 對象使用 Base64URL 算法(詳見後文)轉成字符串。ios

咱們先看看jwt的真實面目git

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwibmFtZSI6ImFkbWluIiwidXNlcm5hbWUiOiJhZG1pbiIsInBvc2l0aW9uIjoiIiwicGhvbmUiOm51bGwsImVtYWlsIjpudWxsLCJyb2xlIjpbImFkbWluIl0sImF2YXRhciI6Imh0dHA6Ly9pbWcuZG9uZ3FpdWRpLmNvbS91cGxvYWRzL2F2YXRhci8yMDE1LzA3LzI1L1FNMzg3bmg3QXNfdGh1bWJfMTQzNzc5MDY3MjMxOC5qcGciLCJpbnRyb2R1Y3Rpb24iOiIiLCJjcmVhdGVfdGltZSI6IjIwMTctMTEtMDJUMTg6MTU6NDguMDAwWiIsInVwZGF0ZV90aW1lIjoiMjAxNy0xMS0yNlQwNjozMzoxNy4wMDBaIiwiaWF0IjoxNTM5MjQ0NjQ1fQ.cRg7ZAQ-1ZBiJUPDx6naQupUMK2BLHmIusMQZrnqVpG

它是一個很長的字符串,中間用點(.)分隔成三個部分。三個部分依次爲github

Header(頭部)
    Payload(負載)
    Signature(簽名)

即header.payload.sign。web

Header 部分是一個 JSON 對象,描述 JWT 的元數據。
Payload 部分也是一個 JSON 對象,用來存放實際須要傳遞的數據。JWT 規定了7個官方字段,供選用。
Signature 部分是對前兩部分的簽名,防止數據篡改。 正則表達式

關於這三部分的詳解,能夠具體參考阮一峯老師的文章:http://www.ruanyifeng.com/blo...redis

在使用jwt的時候建議放在 HTTP 請求的頭信息Authorization字段裏面,以下算法

Authorization: Bearer <token>

2、jwt的好處

  • 先後端分離:使用JWT做爲接口鑑權不須要前端代碼發佈到後端指定目錄下,能夠徹底跨域,前端項目能夠單獨部署
  • 減輕服務端內存負擔:比起使用session來保存cookie,JWT自身包含了全部信息,經過解密便可驗證(固然啦,這個經過吧session存在redis來避免)
  • 安全性:防止CSRF攻擊
  • 移動端:對於沒法使用cookie的一些移動端,JWT可以正常使用
  • 部署:服務器不須要保存session數據,無狀態,容易擴展

PS:爲何不寫爲何使用jwt呢,由於它其實仍是存在很多缺點的,須要根據使用業務場景肯定,不是全部的場景都適合使用jwt,甚至網上有些帖子都是在評論jwt比較雞肋的。具體能夠看分析
https://juejin.im/entry/59748...json

3、jwt怎麼使用

直接上圖,流程以下
image

咱們以Eggjs項目爲例,使用koa-jwt這個庫
https://github.com/koajs/jwt

後端(以Eggjs項目爲例)

一、在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的插件

二、在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對咱們的接口進行保護

三、在登陸接口裏作好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);
    }

前端

一、登陸時使用用戶名和密碼請求登陸接口,拿到接口返回的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防範。

二、前端在後續請求的時候在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請求一個接口看看效果

image
image

返回結果正常。同時,咱們也驗證一下沒有token的狀況下。咱們手動清除了cookie再請求一次接口
image

接口會返回unauthorizeError,這個是咱們期待的返回結果。固然啦,咱們也能夠在後端catch這個錯誤,返回更加友好的信息,例如401,讓前端提示會話過時並自動跳轉到登陸頁。咱們簡單演示這一步就先跳過了。

到這裏,基於jwt的先後端分離實現方案就搞定啦!

4、關於jwt的一些思考

實際上,jwt在使用的過程當中有一個比較致命的缺點,就是一旦 JWT 簽發了,在到期以前就會始終有效,除非服務器部署額外的邏輯。這對於要臨時禁止某個用戶的操做,修改某個用戶權限並立刻生效的業務場景,是知足不了的,而對於作得比較完善的業務系統來說都會有相似的需求,因此是否使用jwt,還須要謹慎評估。

JWT 的最佳用途是「一次性受權 Token」,這種場景下的 Token 的特性以下:
有效期短,只但願被使用一次。
例如分享一個文件給朋友,在指定1小時打開有效。

結語

以上是關於基於jwt的先後端分離實現方案的總結和思考。此外分享一個accesstoken的方案,能夠做爲jwt的替代方案,詳情能夠查看loopback框架的Authorization,能夠知足大部分的業務場景。
https://loopback.io/doc/en/lb...

相關文章
相關標籤/搜索