Express + JWT用戶認證最輕實踐

最近給本身列了一個list,Ummm...列來列去大概是下面這個樣子:前端

  • React SSR服務端渲染
  • jwt用戶認證
  • Vue全家桶
  • 微信小程序開發
  • ... 等等

好吧,誰讓本身菜呢,沒什麼好抱怨的,一個一個來吧。正好最近看了一些token作身份認證的文章,發現其中大部分都是說token登陸怎麼怎麼好,反正沒有幾個認認真真的實現的。。。正好,秉着我是小白我怕誰的原則,繼續分享一下express + jwt的填坑經歷。爲何題目起名是最輕實踐呢?由於確實看完這個你能夠大概理解token登陸的好處以及如何簡單的實現一個先後端經過token進行認證的小系統。這個demo是在我第一篇文章那個腳手架上跑起來的,感興趣的還能夠回顧一下----->express-react-scaffold。具體實現就是下面這個樣子:react

  • 不用token驗證的頁面正常瀏覽
  • 須要驗證的頁面進行token驗證
  • 沒有token信息或token信息過時,提示用戶從新登陸,跳轉到登陸頁面
  • 登陸成功以後每次請求攜帶token信息

這篇文章包括

  • 爲何要用token作身份驗證(另外一種模式是session)
  • 前端http請求攔截器的設置
  • 後端express + jsonwebtoken實現基於token的用戶身份驗證

token是個啥子東西

身份認證的兩種方式

在先後端分離的系統中,身份認證是十分重要的,目前經常使用的兩種身份認證方式以下:ios

  • 基於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的本質實際上就是一個字符串,它有三部分組成頭部+載荷+簽名。git

// 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拿下來的圖來展現,就是下面這個過程,說的很詳細,此外還有一些細節的東西,好比什麼形式存儲,放在頭部哪裏,客戶端要存儲在哪裏等,官網都有比較詳細的介紹,你們能夠去看看。 github

先後端如何用這個東西作身份認證

思路

接下來要詳細的說如何使用jwt來進行先後端的身份驗證了,具體思路以下:web

  • 用戶登陸註冊的邏輯不須要身份驗證,由於沒有用戶的身份信息和登陸狀態;
  • 用戶登陸以後後端生成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中間件express

// routes/index.js
// 全部請求過來都會進行身份驗證
router.use(jwtAuth);
// 路由中間件
router.use((req, res, next) => {
  // 任何路由信息都會執行這裏面的語句
  console.log('this is a api request!');
  // 把它交給下一個中間件,注意中間件的註冊順序是按序執行
  next();
});
複製代碼

後端邏輯部分所有完成,下面是前端的實現部分。json

  • 前端: axios攔截器 + localStorage存儲token 前端主要作的就是兩件事:

第1、把登錄成功以後返回的token存在客戶端,可使用localStorage也可使用cookie,我看官方推薦使用localStorage,我這邊也就用localStorage吧。 第2、每次請求把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.

能夠看到,登陸成功後,token被存放在localStorage裏而且每一次請求都會將token放在頭部Authorization字段內。若是咱們把token從localStorage清除,再次訪問就會報錯。

總結

很是簡單的一個小栗子,也沒什麼技術含量的文章,就當寫着玩練習文筆了。代碼沒有另外放在哪?就在express-react-scaffold上增長的登陸註冊和token認證。能夠經過/login來訪問登錄部分邏輯以及token驗證功能。 O(∩_∩)O哈哈~

相關文章
相關標籤/搜索