ThinkJS JWT 鑑權實踐

編者注:我相信鑑權應該是大部分 Web 服務必備的基礎功能之一。實現權限驗證的方式有不少種,其中 JSON Web Token(即JWT)這種使用 Token 驗證的方式受到了愈來愈多開發者的喜好。其相對於傳統的驗證方式來講會更爲安全一點,並且相對而言因爲加密串中就包含了權限信息,因此不須要額外的數據庫查詢。今天咱們請來了 ThinkJS 的開發人員盧士傑同窗給咱們實戰講解下載 ThinkJS 中如何使用 JWT 權限驗證服務。html


JSON Web Token(JWT)是一個很是輕巧的規範。這個規範容許咱們使用JWT在用戶和服務器之間傳遞安全可靠的信息。它提供基於JSON 格式的 Token 來作安全認證。web

JWT 組成

JWT 由三部分組成,分別是 header(頭部),payload(載荷),signature(簽證) 這三部分以小數點鏈接起來。算法

本例中使用名爲jwt-token的cookie來存儲JWT例如:數據庫

jwt-token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibHVzaGlqaWUiLCJpYXQiOjE1MzI1OTUyNTUsImV4cCI6MTUzMjU5NTI3MH0.WZ9_poToN9llFFUfkswcpTljRDjF4JfZcmqYS0JcKO8;

複製代碼

其中:json

部分
header eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
payload eyJuYW1lIjoibHVzaGlqaWUiLCJpYXQiOjE1MzI1OTUyNTUsImV4cCI6MTUzMjU5NTI3MH0
signature WZ9_poToN9llFFUfkswcpTljRDjF4JfZcmqYS0JcKO8

header

header 是對類型和哈希算法進行base64Encode以後獲得的。對於比例中的header進行base64Decode能夠獲得:安全

{
  "alg":"HS256」,
  "typ":」JWT"
}

複製代碼

payload

payload 是對咱們須要傳輸的信息進行base64Encode以後獲得的。對於本例中的payload進行base64Decode能夠獲得:服務器

{
  "name":"lushijie」,
  "iat":1532595255, // JWT 發佈的時間
  "exp」:1532595270 // JWT 過時的時間,15秒後過時
}

複製代碼

本例中的iat, exp 是 koa-jwt 中的默認字段,除此以外 JWT 標準中註冊的非強制使用的聲明還有 jti,iss等,有興趣的小夥伴能夠查看更多的相關標準。 因爲 payload 能夠在客戶端解碼得到,因此不建議在 payload 中存放敏感信息,例如用戶的密碼。cookie

signature

signature 包含了 header,payload 和 密鑰,計算公式以下:app

const encodedString = base64Encode(header) + "." + base64Encode(payload);
let signature = HMACSHA256(encodedString, '密鑰');

複製代碼

這裏密鑰是保存在服務端的,客戶端是不知道的。koa

JWT 驗證

對於驗證一個 JWT 是否有效也是比較簡單的,服務端根據前面介紹的計算方法計算出 signature,和要校驗的JWT中的 signature 部分進行對比就能夠了,若是 signature 部分相等則是一個有效的 JWT。

JWT 在 ThinkJS 中的實踐

下面咱們在 ThinkJS 中實現使用 JWT 實現只有在登陸後才能訪問一個接口。 ThinkJS 兼容 koa2 的全部middleware,那就找個現成的 jwt 插件吧,這裏咱們使用 koa-jwt 插件。koa-jwt 代碼沒有幾行,你們能夠稍微讀一下,簡單易懂,接下來咱們開始使用它~ 首先咱們要在 ThinkJS 中配置 koa-jwt:

公共配置

/src/config/config.js:

module.exports = {
    // ...
    jwt: {
      secret: 'lushijie-password',
      cookie: 'jwt-token',
      expire: 30 // 秒
    },
  }

複製代碼

由於這三個參數在不一樣的位置會用到,爲了統一管理咱們提取到了公共的 config 中。

中間件配置

/src/config/middleware.js

const jwt = require('koa-jwt');
const isDev = think.env === 'development';

module.exports = [
  // ...
  {
    handle: jwt,
    // match(ctx) {
      // return !/^\/index\/login/.test(ctx.path);
    // },
    options: {
      cookie: think.config('jwt')['cookie'],
      secret: think.config('jwt')['secret'],
      passthrough: true
    }
  },

  // payload 這裏配置由於本例中 jwt 並無用到 request 解析後的參數
];

複製代碼

起初我想經過配置 match 參數來決定某個 URL 是否須要登陸認證,後來發現這樣須要配置好多的正則,比較麻煩; 其次 koa-jwt 沒有提供無權訪問自定義錯誤的鉤子,因此放棄了 match 的方案。

這裏採用了 koa-jwt 提供的配置 passthrough: true,這個參數讓咱們無論權限驗證經過與否均可以繼續執行後面的中間件,只是在當前的 ctx 上設置了 payload。

咱們錯誤處理須要在 logic 層進行,而不該該在 controller 層,不然會出現如下問題:若是 logic 層有有參數校驗不經過同時無權訪問,會先報參數校驗不經過信息,而後再報無權訪問,這顯然是不符合要求的。

擴展 think.Controller

這裏咱們對 think.Controller 作了擴展,這裏沒有對 think.Logic 上進行擴展是由於 think.Logic 繼承自 think.Controller。

/src/extend/controller.js

const jsonwebtoken = require('jsonwebtoken');
module.exports = {
  authFail() {
    return this.fail('JWT 驗證失敗');
  },

  checkAuth(target, name, descriptor) {
    const action = descriptor.value;
    descriptor.value = function() {
      console.log(this.ctx.state.user);
      const userName = this.ctx.state.user && this.ctx.state.user.name;
      if (!userName) {
        return this.authFail();
      }
      this.updateAuth(userName);
      return action.apply(this, arguments);
    }
    return descriptor;
  },

  updateAuth(userName) {
    const userInfo = {
      name: userName
    };
    const {secret, cookie, expire} = this.config('jwt');
    const token = jsonwebtoken.sign(userInfo, secret, {expiresIn: expire});
    this.cookie(cookie, token);
    return token;
  }
}

複製代碼

其中 authFail 是 JWT 驗證失敗的操做;updateAuth 是更新 JWT,此處使用 jsonwebtoken 生成 JWT 並種 cookie;checkAuth 使用了 decorator 方式實現,固然你也可使用你喜歡的方式。

此處使用 cookie 的方式記錄生成的JWT, 當初也能夠採用別的方式儲存,koa-jwt 提供了 getToken 讓咱們可以自由的獲取 JWT, 此處再也不詳述。

controller 業務邏輯

/src/controller/jwt1.js

const userList = {
  lushijie: '123123',
  xiaoming: '456456'
};

module.exports = class extends think.Controller {
  async userAction() {
    const userInfo = this.ctx.state.user;
    if (userInfo) {
      return this.success(userInfo);
    } else {
      return this.fail('獲取用戶信息失敗');
    }
  }

  loginAction() {
    const {name, password} = this.get();
    if (userList[name] && password === userList[name]) {
      const token = this.updateAuth(name);
      return this.success(token);
    } else {
      return this.fail('登陸失敗');
    }
  }

  logoutAction() {
    this.updateAuth(null);
    return this.success('退出登陸成功');
  }
}

複製代碼

jwt1 這個簡單的 controller 包含了三個簡單的功能,登陸、退出與獲取用戶信息,其中獲取用戶信息要求必須登陸以後才能夠訪問。 這裏的登陸只是進行了一個簡單的模擬,真實項目中的用戶驗證會比這個複雜一些,原理是一致的。

Logic 權限驗證

/src/logic/jwt1.js

const {checkAuth} = think.Controller.prototype;
module.exports = class extends think.Logic {

  @checkAuth
  userAction(){
    // 正常的參數驗證邏輯
  }
}

複製代碼

這樣一個驗證就完成了! 若是該 Logic 中的全部 action 都須要進行驗證,只須要給 __before 加 decorator 就能夠了,其餘的 action 就不用加了!

相關文章
相關標籤/搜索