1. 程式人生 > 其它 >JWT令牌

JWT令牌

目錄

單點登入

一、使用者身份認證

1、單一伺服器模式

一般過程如下:

  1. 使用者向伺服器傳送使用者名稱和密碼。
  2. 驗證伺服器後,相關資料(如使用者名稱,使用者角色等)將儲存在當前會話(session)中。
  3. 伺服器向用戶返回session_id,session資訊都會寫入到使用者的Cookie。
  4. 使用者的每個後續請求都將通過在Cookie中取出session_id傳給伺服器。
  5. 伺服器收到session_id並對比之前儲存的資料,確認使用者的身份。

缺點:

  • 單點效能壓力,無法擴充套件。
  • 分散式架構中,需要session共享方案,session共享方案存在效能瓶頸。

session共享方案:

session廣播:效能瓶頸,不推薦

redis代替session:推薦,效能高

2、SSO(Single Sign On)模式

CAS單點登入、OAuth2

分散式,SSO(single sign on)模式:單點登入英文全稱Single Sign On,簡稱就是SSO。它的解釋是:在多個應用系統中,只需要登入一次,就可以訪問其他相互信任的應用系統。

  • 如圖所示,圖中有3個系統,分別是業務A、業務B、和SSO。
  • 業務A、業務B沒有登入模組。
  • 而SSO只有登入模組,沒有其他的業務模組。

一般過程如下:

  1. 當業務A、業務B需要登入時,將跳到SSO系統。
  2. SSO從使用者資訊資料庫中獲取使用者資訊並校驗使用者資訊,SSO系統完成登入。
  3. 然後將使用者資訊存入快取(例如redis)。
  4. 當用戶訪問業務A或業務B,需要判斷使用者是否登入時,將跳轉到SSO系統中進行使用者身份驗證,SSO判斷快取中是否存在使用者身份資訊。
  5. 這樣,只要其中一個系統完成登入,其他的應用系統也就隨之登入了。這就是單點登入(SSO)的定義。

優點 :

使用者身份資訊獨立管理,更好的分散式管理。可以自己擴充套件安全策略

缺點:

認證伺服器訪問壓力較大。

3、Token模式

優點:

  • 無狀態: token是無狀態,session是有狀態的
  • 基於標準化:你的API可以採用標準化的 JSON Web Token (JWT)

缺點:

  • 佔用頻寬
  • 無法在伺服器端銷燬

二、訪問令牌的型別

三、JWT令牌

1、什麼是JWT令牌

JWT是JSON Web Token的縮寫,即JSON Web令牌,是一種自包含令牌。 
JWT的使用場景:
一種情況是webapi,類似之前的阿里雲播放憑證的功能另一種情況是多web伺服器下實現無狀態分散式身份驗證JWT官網有一張圖描述了JWT的認證過程JWT的作用:JWT 最重要的作用就是對 token資訊的防偽作用JWT的原理:一個JWT由三個部分組成:JWT頭、有效載荷、簽名雜湊最後由這三者組合進行base64編碼得到JWT

2、JWT令牌的組成

典型的,一個JWT看起來如下圖:

https://jwt.io/

該物件為一個很長的字串,字元之間通過"."分隔符分為三個子串。

每一個子串表示了一個功能塊,總共有以下三個部分:JWT頭、有效載荷和簽名

JWT頭

JWT頭部分是一個描述JWT元資料的JSON物件,通常如下所示。

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

在上面的程式碼中,alg屬性表示簽名使用的演算法,預設為HMAC SHA256(寫為HS256);typ屬性表示令牌的型別,JWT令牌統一寫為JWT。最後,使用Base64 URL演算法將上述JSON物件轉換為字串儲存。

有效載荷

有效載荷部分,是JWT的主體內容部分,也是一個JSON物件,包含需要傳遞的資料。 JWT指定七個預設欄位供選擇。

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

除以上預設欄位外,我們還可以自定義私有欄位,如下例:

{
    "name": "Helen",
    "admin": true,
    "avatar": "helen.jpg"
}

請注意,預設情況下JWT是未加密的,任何人都可以解讀其內容,因此不要構建隱私資訊欄位,存放保密資訊,以防止資訊洩露。

JSON物件也使用Base64 URL演算法轉換為字串儲存。

簽名雜湊

簽名雜湊部分是對上面兩部分資料簽名,通過指定的演算法生成雜湊,以確保資料不會被篡改。

首先,需要指定一個密碼(secret)。該密碼僅僅為儲存在伺服器中,並且不能向用戶公開。然後,使用標頭中指定的簽名演算法(預設情況下為HMAC SHA256)根據以下公式生成簽名。

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret)    ==>   簽名hash

在計算出簽名雜湊後,JWT頭,有效載荷和簽名雜湊的三個部分組合成一個字串,每個部分用"."分隔,就構成整個JWT物件。

Base64URL演算法

如前所述,JWT頭和有效載荷序列化的演算法都用到了Base64URL。該演算法和常見Base64演算法類似,稍有差別。

作為令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三個字元是"+","/"和"=",由於在URL中有特殊含義,因此Base64URL中對他們做了替換:"="去掉,"+"用"-"替換,"/"用"_"替換,這就是Base64URL演算法。

注意:base64編碼,並不是加密,只是把明文資訊變成了不可見的字串。但是其實只要用一些工具就可以把base64編碼解成明文,所以不要在JWT中放入涉及私密的資訊。

3、JWT的用法

客戶端接收伺服器返回的JWT,將其儲存在Cookie或LocalStorage中。

此後,客戶端將在與伺服器互動中都會攜帶JWT。如果將它儲存在Cookie中,就可以自動傳送,但是不會跨域,因此一般是將它放入HTTP請求的Header Authorization欄位中。

當跨域時,也可以將JWT放置於POST請求的資料主體中。

四、JWT問題和趨勢

1、JWT預設不加密,但可以加密。生成原始令牌後,可以使用該令牌再次對其進行加密。

2、當JWT未加密時,一些私密資料無法通過JWT傳輸。

3、JWT不僅可用於認證,還可用於資訊交換。善用JWT有助於減少伺服器請求資料庫的次數。

4、JWT的最大缺點是伺服器不儲存會話狀態,所以在使用期間不可能取消令牌或更改令牌的許可權。也就是說,一旦JWT簽發,在有效期內將會一直有效。

5、JWT本身包含認證資訊,因此一旦資訊洩露,任何人都可以獲得令牌的所有許可權。為了減少盜用,JWT的有效期不宜設定太長。對於某些重要操作,使用者在使用時應該每次都進行身份驗證。

6、為了減少盜用和竊取,JWT不建議使用HTTP協議來傳輸程式碼,而是使用加密的HTTPS協議進行傳輸。

五、測試JWT

JWT依賴

<!-- JWT -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.7.0</version>
</dependency>

1、生成token

package com.atguigu.jwt;
public class JwtTests {
    //過期時間,毫秒,24小時
    private static long tokenExpiration = 24*60*60*1000;
    //祕鑰
    private static String tokenSignKey = "hguo123";
    @Test
    public void testCreateToken(){
        String token = Jwts.builder()
            // 頭
            .setHeaderParam("typ", "JWT") //令牌型別
            .setHeaderParam("alg", "HS256") //簽名演算法
            // 載荷:預設資訊
            .setSubject("guli-user") //令牌主題
            .setIssuer("atguigu") //簽發者
            .setAudience("atguigu") //接收者
            .setIssuedAt(new Date()) //簽發時間
            .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) //過期時間
            .setNotBefore(new Date(System.currentTimeMillis() + 20*1000)) //20秒後可用
            .setId(UUID.randomUUID().toString())
            // 載荷:自定義資訊
            .claim("nickname", "Helen")
            .claim("avatar", "1.jpg")
            // 簽名雜湊
            .signWith(SignatureAlgorithm.HS256, tokenSignKey)
            // 組裝JWT字串
            .compact();
        System.out.println(token);
    }
}

2、解析token

@Test
public void testGetUserInfo(){
    //祕鑰
    private static String tokenSignKey = "hguo123";

    String token = "jwt字串";
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
    // 取出有效載荷
    Claims claims = claimsJws.getBody();
    String subject = claims.getSubject();
    String issuer = claims.getIssuer();
    String audience = claims.getAudience();
    Date issuedAt = claims.getIssuedAt();
    Date expiration = claims.getExpiration();
    Date notBefore = claims.getNotBefore();
    String id = claims.getId();

    // 取出自定義有效載荷
    String nickname = (String)claims.get("nickname");
    String avatar = (String)claims.get("avatar");
    
    System.out.println(subject);
    System.out.println(issuer);
    System.out.println(audience);
    System.out.println(issuedAt);
    System.out.println(expiration);
    System.out.println(notBefore);
    System.out.println(id);
    System.out.println(nickname);
    System.out.println(avatar);
}