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

JWT 訪問令牌

JWT 訪問令牌

更為詳細的介紹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)模式

分散式,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的認證過程(可以看上面token認證的那張圖)

jwt的作用:

  • JWT 最重要的作用就是對 token資訊的防偽作用

JWT的原理:

  • 一個JWT由三個部分組成:JWT頭、有效載荷、簽名雜湊
  • 最後由這三者組合進行base64編碼得到JWT

2、JWT令牌的組成

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

https://jwt.io/

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

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

注意:該字串只有jwt頭部分不能被解析(通過加密的方式) 其他的兩個部分 都可以(只通過Base64 URL編碼 並沒有被加密)

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演算法

簡單描述 就是會替換url中具有特殊含義的字元 用其他代替

如前所述,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

一、建立Maven專案

1、專案

專案型別:Maven

groupId:look.word

artifactId:jwt

2、基本依賴

<dependencies>
    <!--jwt依賴-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.0</version>
            </dependency>

    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

</dependencies>

JWT工具類

/**
 * JWT工具類
 */
public class JwtUtil {

    //有效期為
    public static final Long JWT_TTL = 24*60 * 60 *1000L;// 60 * 60 *1000  一個小時
    //設定祕鑰明文
    public static final String JWT_KEY = "sangeng";

    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }
    
    /**
     * 生成jtw
     * @param subject token中要存放的資料(json格式)
     * @return
     */
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 設定過期時間
        return builder.compact();
    }

    /**
     * 生成jtw
     * @param subject token中要存放的資料(json格式)
     * @param ttlMillis token超時時間
     * @return
     */
    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 設定過期時間
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)              //唯一的ID
                .setSubject(subject)   // 主題  可以是JSON資料
                .setIssuer("sg")     // 簽發者
                .setIssuedAt(now)      // 簽發時間
                .signWith(signatureAlgorithm, secretKey) //使用HS256對稱加密演算法簽名, 第二個引數為祕鑰
                .setExpiration(expDate);
    }

    /**
     * 建立token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 設定過期時間
        return builder.compact();
    }

    public static void main(String[] args) throws Exception {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
        Claims claims = parseJWT(token);
        System.out.println(claims);
    }

    /**
     * 生成加密後的祕鑰 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
    
    /**
     * 解析
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }


}