1. 程式人生 > >基於前後端分離的身份認證方式——JWT

基於前後端分離的身份認證方式——JWT

目錄:
一、什麼是JWT
二、我們為什麼要使用JWT(與傳統的session認證有何區別)
三、如何使用JWT
四、JWT的構成及原理
五、JWT加解密例項

一、什麼是JWT
JWT——Json web token
是為了在網路應用環境間傳遞宣告而執行的一種基於JSON的開放標準,可實現無狀態、分散式的Web應用授權。

二、我們為什麼需要JWT?
首先,當前後端分離時我們會因為同源策略而無法設定cookie和sessionid。當然了我們有很多方式去解決這個問題,比如反向代理和jsonp等。但這仍然不如直接使用jwt來的簡便。其次就是要說到jwt與傳統的身份認證相比有什麼優勢了。
回答這個問題需要來看看基於token的認證和傳統的session認證的區別
1、傳統的session認證

我們知道,http協議本身是一種無狀態的協議,而這就意味著如果使用者向我們的應用提供了使用者名稱和密碼來進行使用者認證,那麼下一次請求時,使用者還要再一次進行使用者認證才行,因為根據http協議,我們並不能知道是哪個使用者發出的請求,所以為了讓我們的應用能識別是哪個使用者發出的請求,我們只能在伺服器儲存一份使用者登入的資訊,這份登入資訊會在響應時傳遞給瀏覽器,告訴其儲存為cookie,以便下次請求時傳送給我們的應用,這樣我們的應用就能識別請求來自哪個使用者了,這就是傳統的基於session認證。

但是這種基於session的認證使應用本身很難得到擴充套件,隨著不同客戶端使用者的增加,獨立的伺服器已無法承載更多的使用者,而這時候基於session認證應用的問題就會暴露出來.

2、基於token的鑑權機制

基於token的鑑權機制類似於http協議也是無狀態的,它不需要在服務端去保留使用者的認證資訊或者會話資訊。這就意味著基於token認證機制的應用不需要去考慮使用者在哪一臺伺服器登入了,這就為應用的擴充套件提供了便利。

流程上是這樣的:

1、使用者使用使用者名稱密碼來請求伺服器 2、
2、伺服器進行驗證使用者的資訊 伺服器通過驗證傳送給使用者一個token
3、 客戶端儲存token,並在每次請求時附送上這個token值 服務端驗證token值,並返回資料
4、這個token必須要在每次請求時傳遞給服務端,它應該儲存在請求頭裡,
另外,服務端要支援CORS(跨來源資源共享)策略,一般我們在服務端這麼做就可以了Access-Control-Allow-Origin:*。

也就是說相比於傳統基於cookie的session,基於token的jwt有以下優點:

Cookie是不允許垮域訪問的,這一點對Token機制是不存在的,前提是傳輸的使用者認證資訊通過HTTP頭傳輸.
無狀態(也稱:服務端可擴充套件行):Token機制在服務端不需要儲存session資訊,因為Token 自身包含了所有登入使用者的資訊,只需要在客戶端的cookie或本地介質儲存狀態資訊.
更適用CDN: 可以通過內容分發網路請求你服務端的所有資料(如:javascript,HTML,圖片等),而你的服務端只要提供API即可.
去耦: 不需要繫結到一個特定的身份驗證方案。Token可以在任何地方生成,只要在你的API被呼叫的時候,你可以進行Token生成呼叫即可.
更適用於移動應用: 當你的客戶端是一個原生平臺(iOS, Android,Windows 8等)時,Cookie是不被支援的(你需要通過Cookie容器進行處理),這時採用Token認證機制就會簡單得多。
CSRF:因為不再依賴於Cookie,所以你就不需要考慮對CSRF(跨站請求偽造)的防範。
效能: 一次網路往返時間(通過資料庫查詢session資訊)總比做一次HMACSHA256計算 的Token驗證和解析要費時得多.
不需要為登入頁面做特殊處理: 如果你使用Protractor 做功能測試的時候,不再需要為登入頁面做特殊處理.
基於標準化:你的API可以採用標準化的 JSON Web Token (JWT). 這個標準已經存在多個後端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支援(如:Firebase,Google, Microsoft).

三、如何使用jwt

安裝方式:

//--save是否需要取決於你是否需要把安裝版本寫進package.json
npm install --save)jwt-simple

使用方式

var jwt = require('jwt-simple');
var payload = { foo: 'bar' };
var secret = 'xxx';

// HS256 secrets are typically 128-bit random strings, for example hex-encoded: 
// var secret = Buffer.from('fe1a1915a379f3be5394b64d14794932', 'hex) 

// encode 
var token = jwt.encode(payload, secret);

// decode 
var decoded = jwt.decode(token, secret);
console.log(decoded); //=> { foo: 'bar' } 

注意secret是用於解密的密文,是必須儲存在伺服器端。

四、JWT的原理及構成
JWT的構成:
jwt由三部分構成:頭部(header)、載荷(payload, )、簽證(signature).

頭部:jwt的頭部承載兩部分資訊:

宣告型別,這裡是jwt
宣告加密的演算法 通常直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然後將頭部進行base64加密(該加密是可以對稱解密的),構成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload

載荷:就是存放有效資訊的地方。,這些有效資訊包含三個部分

標準中註冊的宣告
公共的宣告
私有的宣告
標準中註冊的宣告 (建議但不強制使用) :

iss: jwt簽發者
sub: jwt所面向的使用者
aud: 接收jwt的一方
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
nbf: 定義在什麼時間之前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。

公共的宣告 :
公共的宣告可以新增任何的資訊,一般新增使用者的相關資訊或其他業務需要的必要資訊.但不建議新增敏感資訊,因為該部分在客戶端可解密.

私有的宣告 :
私有宣告是提供者和消費者所共同定義的宣告,一般不建議存放敏感資訊,因為base64是對稱解密的,意味著該部分資訊可以歸類為明文資訊。

定義一個payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然後將其進行base64加密,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature

簽證:這個簽證資訊由三部分header (base64後的)
payload (base64後的)
secret分需要base64加密後的header和base64加密後的payload使用.連線組成的字串,然後通過header中宣告的加密方式進行加鹽secret組合加密,然後就構成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

將這三部分用.連線成一個完整的字串,構成了最終的jwt:

五、jwt運用例項

/**
 * 身份驗證.
 */
const jwt = require('jwt-simple');
const moment = require('moment');

//加密過程
var getJwt = function(userId,secret)
{
  var expires = moment().add( 7,'days').valueOf();
  var token = jwt.encode({
    iss: userId,
    exp: expires
  }, secret);
  return token;
}

//解密過程
var testJwt = function(token,secret){
  //返回{foo: 'bar'}
  var decoded = jwt.decode(token,secret)
  return decoded;
}

module.exports = {
  create: getJwt,
  test: testJwt
}