JSON Web Token理解

 

 

一:理解單系統登陸的原理及實現?html

web應用採用的 browser/server 架構的,http是無狀態協議的,也就是說用戶從A頁面跳轉到B頁面會發起http請求,當服務器返回響應後,當用戶A繼續訪問其餘頁面的時候,服務器端沒法獲知該狀態,所以會使用cookie/session來記錄用戶狀態的。node

session認證狀態的基本原理:當客戶端向服務器端請求時,會建立一個session標識存在客戶端的cookie當中,每次請求的時候會將該標識隨cookie一塊兒發送到服務器端,服務器端會首先檢查這個客戶端的請求裏面是否包含了一個session的標識,若是已經包含了,那麼服務器端就會根據該session標識來判斷用戶的狀態,不然的話,服務器端會建立一個新的session標識傳給客戶端的cookie當中,之後每次客戶端請求的時候,會從cookie中獲取該標識傳遞過去。git

二:理解單點登陸的原理?github

上面使用session/cookie 能夠實現單個系統的登陸,那若是是多個系統的話,怎麼辦?難道須要用戶一個個去登陸? 一個個去註銷?咱們須要作的是,不管系統有多少個,咱們只須要登陸一次就夠了,其餘的相關的系統均可以登陸/註銷一次便可。web

單系統登陸解決方案的核心是cookie,cookie會攜帶服務器端返回的sessionId, 在瀏覽器與服務器端維護會話狀態。可是咱們知道cookie是有限制的,cookie有域的概念,瀏覽器發送http請求時會自動匹配該本站點的cookie域。而不是全部的cookie。算法

那麼既然這樣,咱們很容易想到的是,咱們能夠把全部子系統的域名都放在一個頂級域名下不就能夠了?好比 "*.taobao.com",而後將他們的cookie域設置爲 "taobao.com", 可是這種並很差:json

第一:由於全部系統的域名須要統一,好比淘寶和天貓的域名就不相同;
第二:應用羣各個系統所使用的技術須要相同,好比tomcat服務器叫JESSIONID, 其餘的服務器可能不叫這個標識。
第三:cookie的安全性不高的。後端

所以咱們須要一種全新的方式來實現多系統應用羣的登陸,這就是單點登陸。跨域

什麼是單點登陸?單點登陸的全稱是 Single Sign On (能夠簡稱爲SSO), 在多個系統中只要登陸一次,即可以在其餘全部系統中獲得受權而無需再次登陸。瀏覽器

SSO有一個獨立的認證中心,認證中心它能夠接受用戶的用戶名密碼等安全信息,其餘的地方不接受登陸入口,只接受認證中心的間接受權,間接受權它是經過令牌實現的。受權令牌做爲參數會發送到各個子系統,子系統拿到令牌,所以會獲得了受權,所以就能夠建立了局部的會話。局部會話和單系統登陸的原理很相似的。

下面咱們來打個比方理解單點登陸的基本原理:

第一步:我想登陸A系統,A系統發現用戶未登陸,所以咱們須要他們跳轉到SSO認證中心(且將本身請求的地址做爲參數傳遞過去)。SSO認證中心發現未登陸,會將用戶引導到登陸頁面。

第二步:用戶輸入用戶名和密碼提交申請登陸,SSO認證中心會檢測用戶名和密碼信息,若是用戶名和密碼正確的話,那麼用戶和SSO認證中心之間會建立一個局部會話,而且建立一個受權令牌。sso認證中心會帶着該令牌跳轉到A系統那個請求的地址去。

第三步:A系統會檢測該令牌,若是有效的話,就會跳到用戶輸入的地址頁面去,不然,仍是返回登陸頁面,提示錯誤信息。

第四步:用戶訪問系統B,系統B發現用戶未登陸,會跳轉到SSO認證中心(將本身請求的地址做爲參數傳遞過去),sso認證中心發現用戶已經登陸了,會跳轉回系統B的那個地址去,並帶上令牌,系統B拿到令牌,就會去sso認證中心去校驗該令牌是否有效。
若是有效的話,說明認證成功了,就會跳轉到系統B訪問地址的頁面上去。

用戶如今已經登陸成功了,sso認證中心會與各個子系統創建會話,用戶與sso認證中心創建的會話被稱爲全局會話,用戶與各個
子系統創建的會話被稱爲局部會話,局部會話創建以後,用戶訪問子系統資源後就不會再經過sso認證中心了。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

三:什麼是JSON Web Token?

JSON Web Token 是一個開放標準協議,它定義了一種緊湊和自包含的方式,它用於各方之間做爲JSON對象安全地傳輸信息。

它有以下優勢:

1. 能夠適用於分佈式的單點登陸場景。
2. 可使用跨域認證解決方案。
3. jwt實現自動刷新token的方案(待認證)。

JSON Web Token,它定義了一種緊湊和自包含的方式,如何理解緊湊和自包含呢?

緊湊:就是說這個數據量比較少,而且能經過url參數,http請求提交的數據以及http header的方式來傳遞。
自包含:這個串能夠包含不少信息,好比用戶id,訂單號id等,若是其餘人拿到該信息,就能夠拿到關鍵業務信息。

3.1)JWT的基本原理,基本流程以下:

1. 客戶端使用帳號和密碼請求登陸接口。
2. 登陸成功後服務器使用簽名密鑰生成JWT,而後返回JWT給客戶端。
3. 客戶端再次向服務端請求其餘接口時會帶上JWT。
4. 服務器接收到JWT後驗證簽名的有效性,對客戶端作出相應的響應。

3.2)JWT與session的區別?

   session是基於cookie來傳輸的,session信息是存儲在服務器端中,客戶端向服務器端發請求時,服務器端會返回一個jessionId給客戶端中的cookie中,之後每次請求都會從cookie中的jessionid傳遞過去,服務器經過cookie中的sessionid獲取到當前會話的用戶,對於單系統來說這是沒有問題的,可是對於多個系統的話就涉及到session如何共享的問題了,而且隨着認證用戶增多的話,session會佔用大量服務器內存。

JWT是存儲在客戶端的,服務器端不須要存儲JWT,JWT含有用戶id,服務器拿到jwt驗證後就能夠拿到用戶信息了,jwt是無狀態的,它不與任何機器綁定的,只要簽名密鑰足夠的安全就能保證jwt的可靠性。

3.3)JWT中的token與session中的token安全性比較

session 中安全性問題:

服務器端執行session機制的時候會生成session的口令,在Tomcat服務器中,默認會採用 jsessionid 這個值,可是在其餘服務器上會有所不一樣,好比Connect默認會叫 connect_uid, 咱們通常把一些敏感的信息放在cookie中是不可取的,可是將口令放在cookie中仍是能夠的,若是口令被篡改了的話,就丟失了映射關係,也沒法修改服務器端存在的數據,而且session的有效期通常爲20/30分鐘,若是在該時間以內客戶端和服務器端沒有產生任何交互,服務器端會自動將session自動清空,所以session中想要維護用戶一直登錄的狀態的話,須要客戶端每隔20分鐘使用setInterval自動發一個請求給服務器端,這樣的話,先後端就有交互,因此就能夠一直保持登錄狀態。不然的話,每次20分鐘後,登錄狀態就會失效,每隔20分鐘用戶須要從新登陸,用戶體驗將會變得很差。session這樣作的最主要的是爲了安全性考慮,有效期的時間很是短,防止黑客攻擊。

JWT方案中安全性問題

jwt是存儲在客戶端的,服務器端不須要存儲jwt的,客戶端每次發送請求時會攜帶該token,而後到服務器端會驗證token是否正確,是否過時了,而後會經過解碼出攜帶的用戶的信息的,可是若是token在傳輸的過程當中被攻擊者截取了的話,那麼對方就能夠僞造請求,利用竊取到的token模擬正常請求,實現用戶的正常操做,而服務器端徹底不知道,由於JWT在服務器端是無狀態的,且服務器端不存儲jwt的。其實jwt解決的問題是認證和受權的問題,對於安全性的話,仍是建議對外公佈的接口使用https.

四:理解JWT的基本數據結構

基本的JWT的數據結構是以下這樣的:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia29uZ3poaSIsImlhdCI6MTU0Mzc1MzczNX0.h1XmQo017udxlFsH-8US9Lg8dJ0IDsSbRbjEN5Nq0l4

如上它是由三部分組成的,中間使用 . 分割成三個部分,它是有 Header(頭部), PayLoad(負載),Signature(簽名)組成的。
如:Header.Payload.Signature

4.1 Header

Header部分是一個JSON對象,描述JWT的元數據,通常是以下的樣子:

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

如上json代碼,alg屬性表示簽名的算法,默認是 HMAC SHA256 (縮寫爲:HS256); typ屬性表示這個令牌(token)的類型爲JWT. 最後將上面的JSON對象使用 Base64URL的算法轉成字符串。
咱們可使用在線的base64編碼轉下(http://tool.oschina.net/encrypt?type=3),以下所示:

如上 alg 部分,默認加密的算法是 HMAC SHA256, 固然咱們也能夠選擇下面的加密算法,加密算法有以下:

4.2 Payload

Payload部分也是一個JSON對象,用來存放實際須要傳遞的數據,官方提供了7個字段,以下:

複製代碼
iss(issuer): 簽發人
exp (expiration time): 過時時間
sub (subject): 主題
aud (audience): 受衆
nbf (Not Before): 生效時間
iat (Issued At): 簽發時間
jti (JWT ID): 編號
複製代碼

payload的中文含義是載荷,它能夠理解爲存放有效信息的地方。這些有效信息通常包含以下三個部分:

4.2.1)標準中註冊的聲明:(如上就是官方提供的7個字段)。

4.2.2)公共的聲明:公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息,可是不建議添加敏感信息,
由於該部分在客戶單可解密。

4.2.3)私有的聲明:私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,該部分信息
能夠理解爲明文信息。

那麼定義一個簡單的 payload 能夠以下結構:

{
  "sub": '123456',
  "name": "kongzhi",
  "admin": true
}

咱們仍是使用如上的base64編碼,會編碼成以下所示:

4.3 Signature

Signature 是對前面兩部分的簽名,防止數據被篡改。簽名是把Header和payload對應的json結構進行base64 編碼以後獲得的兩個串用英文句點號拼接起來的,而後會根據header裏面的alg指定的前面算法(默認是 HMAC SHA256)生成出來的。
如上header部分使用的是 HS256(即HMAC和SHA256),HMAC是用於生成摘要的,SHA256是用於對摘要進行數字簽名的。所以使用HMACSHA256實現signature實現的算法以下:

HMACSHA256(
  base64UrlEncode(header) + '.' + 
  base64UrlEncode(payload),
  secret
)

如上是 Signature 簽名算法,最後一個 secret 是加密的密鑰的含義。所以經過如上的用法咱們就能夠拿到JWT了。

4.4 JWT實踐

JWT的格式是由三個點分割的base64-URL字符串,能夠在html或http環境中傳遞,咱們能夠簡單的使用 https://jwt.io/ 官網來生成一個JWT了,以下是我前面定義的部分數據:

五:node中使用JWT的API

nodejs實現的jwt的github代碼(https://github.com/auth0/node-jsonwebtoken)

它主要有3個方法:

5.1 jwt.sign(payload, secretOrPrivateKey, [options, callback])

payload 參數必須是一個object、Buffer、或 string.
注意:exp(過時時間) 只有當payload是object字面量時才能夠設置。若是payload不是buffer或string,它會被強制轉換爲使用的字符串JSON.stringify()。

secretOrPrivateKey 參數 是包含HMAC算法的密鑰或RSA和ECDSA的PEM編碼私鑰的string或buffer。

options 參數有以下值:

複製代碼
algorithm:加密算法(默認值:HS256)
expiresIn:以秒錶示或描述時間跨度zeit / ms的字符串。如60,"2 days","10h","7d",含義是:過時時間
notBefore:以秒錶示或描述時間跨度zeit / ms的字符串。如:60,"2days","10h","7d"
audience:Audience,觀衆
issuer:Issuer,發行者
jwtid:JWT ID
subject:Subject,主題
noTimestamp
header
複製代碼

該方法若是是異步方法,則會提供回調,若是是同步的話,則將會 JsonWebToken返回爲字符串。
在expiresIn, notBefore, audience, subject, issuer沒有默認值時,能夠直接在payload中使用 exp, nbf, aud, sub 和 iss分別表示。

注意:若是在jwts中沒有指定 noTimestamp的話,在jwts中會包含一個iat,它的含義是使用它來代替實際的時間戳來計算的。

下面咱們在項目中使用node中jsonwebtoken來生成一個JWT的demo了,在index.js 代碼以下:

複製代碼
// 生成一個token
const jwt = require('jsonwebtoken');

const secret = 'abcdef';

let token = jwt.sign({
  name: 'kongzhi'
}, secret, (err, token) => {
  console.log(token);
});
複製代碼

而後咱們進入項目中的目錄,執行 node index.js 執行後看到命令行中會打印中的token了,以下所示:

固然咱們也能夠設置token的過時時間,好比設置token的有效期爲1個小時,以下代碼:

複製代碼
// 生成一個token
const jwt = require('jsonwebtoken');

const secret = 'abcdef';
// 設置token爲一個小時有效期
let token = jwt.sign({
  name: 'kongzhi',
  exp: Math.floor(Date.now() / 1000) + (60 * 60)
}, secret, (err, token) => {
  console.log(token);
});
複製代碼

5.2 jwt.verify(token, secretOrPrivateKey, [options, callback])

該方法是驗證token的合法性

好比上面生成的token設置爲1個小時,生成的token爲:

'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia29uZ3poaSIsImlhdCI6MTU0Mzc2MjYzOSwiZXhwIjoxNTQzNzY2MjM5fQ.6idR7HPpjZIfZ_7j3B3eOnGzbvWouifvvJfeW46zuCw'

下面咱們使用 jwt.verify來驗證一下:

複製代碼
const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia29uZ3poaSIsImlhdCI6MTU0Mzc2MjYzOSwiZXhwIjoxNTQzNzY2MjM5fQ.6idR7HPpjZIfZ_7j3B3eOnGzbvWouifvvJfeW46zuCw';
const secret = 'abcdef';
jwt.verify(token, secret, (error, decoded) => {
  if (error) {
    console.log(error.message);
  }
  console.log(decoded);
});
複製代碼

執行node index.js 代碼後,生成以下信息:

如今咱們再來生成一個token,假如該token的有效期爲30秒,30秒後,我再使用剛剛生成的token,再去使用 verify去驗證下,看是否能驗證經過嗎?(理論上token失效了,是不能驗證經過的,可是咱們仍是來實踐下)。以下代碼:

複製代碼
// 生成一個token
const jwt = require('jsonwebtoken');

const secret = 'abcdef';
// 設置token爲30秒的有效期
let token = jwt.sign({
  name: 'kongzhi',
  exp: Math.floor(Date.now() / 1000) + 30
}, secret, (err, token) => {
  console.log(token);
});
複製代碼

在命令行中生成 jwt爲: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia29uZ3poaSIsImV4cCI6MTU0Mzc2MzU1MywiaWF0IjoxNTQzNzYzNTIzfQ.79rH3h_ezayYBeNQ2Wj8fGK_wqsEqEPgRTG9uGmvD64';

而後咱們如今使用該token去驗證下,以下代碼:

複製代碼
// 生成一個token
const jwt = require('jsonwebtoken');

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia29uZ3poaSIsImV4cCI6MTU0Mzc2MzU1MywiaWF0IjoxNTQzNzYzNTIzfQ.79rH3h_ezayYBeNQ2Wj8fGK_wqsEqEPgRTG9uGmvD64';
const secret = 'abcdef';
jwt.verify(token, secret, (error, decoded) => {
  if (error) {
    console.log(error.message);
  }
  console.log(decoded);
});
複製代碼

執行命令,以下所示:

如上能夠看到token的有效期爲30秒,30秒後再執行的話,就會提示jwt過時了。

5.3 jwt.decode(token, [, options])該方法是 返回解碼沒有驗證簽名是否有效的payload。

相關文章
相關標籤/搜索