Java中詳細使用JWT(JJWT)

 

目錄

1.什麼是JWT

2.JWT什麼時候去使用,有什麼好處

3 JWT問題和趨勢

4 JWT的組成

頭部

有效載荷

簽名

4.java中使用

 


 

 

1.什麼是JWT

JSON Web令牌(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間作爲JSON對象安全地傳輸信息。由於此信息是經過數字簽名的,因此可以被驗證和信任。可以使用祕密(使用HMAC算法)或使用RSAECDSA的公鑰/私鑰對對JWT進行簽名

儘管可以對JWT進行加密以在雙方之間提供保密性,但我們將重點關注已簽名的令牌。簽名的令牌可以驗證其中包含的聲明的完整性,而加密的令牌則將這些聲明隱藏在其他方的面前。當使用公鑰/私鑰對對令牌進行簽名時,簽名還證明只有持有私鑰的一方纔是對其進行簽名的一方。

 

2.JWT什麼時候去使用,有什麼好處

授權: 這是使用JWT的最常見方案。一旦用戶登錄,每個後續請求將包括JWT,從而允許用戶訪問該令牌允許的路由,服務和資源。單一登錄是當今廣泛使用JWT的一項功能,因爲它的開銷很小並且可以在不同的域中輕鬆使用

信息交換: JSONWeb令牌是在各方之間安全地傳輸信息的好方法。因爲可以對JWT進行簽名(例如,使用公鑰/私鑰對),所以您可以確定發件人是他們所說的人。另外,由於簽名是使用標頭和有效負載計算的,因此您還可以驗證內容是否未被篡改。

支持跨域訪問:Cookie是不允許垮域訪問的,這一點對Token機制是不存在的,前提是傳輸的用戶認證信息通過HTTP頭傳輸.

無狀態(也稱:服務端可擴展行):Token機制在服務端不需要存儲session信息,因爲Token 自身包含了所有登錄用戶的信息,只需要在客戶端的cookie或本地介質存儲狀態信息.

去耦:不需要綁定到一個特定的身份驗證方案。Token可以在任何地方生成,只要在你的API被調用的時候,你可以進行Token生成調用即可.(這個似乎也在繼續說前面第一點和第二點的好處。。。)

更適用於移動應用:當你的客戶端是一個原生平臺(iOS, Android,Windows 8等)時,Cookie是不被支持的(你需要通過Cookie容器進行處理),這時採用Token認證機制就會簡單得多。

CSRF:因爲不再依賴於Cookie,所以你就不需要考慮對CSRF(跨站請求僞造)的防範。(如果token是用cookie保存,CSRF還是需要考慮,一般建議使用1、在HTTP請求中以參數的形式加入一個服務器端產生的token。或者2.放入http請求頭中也就是一次性給所有該類請求加上csrftoken這個HTTP頭屬性,並把token值放入其中) 

基於標準化:你的API可以採用標準化的 JSON Web Token (JWT). 這個標準已經存在多個後端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)

性能:JWT不僅可用於認證,還可用於信息交換。善用JWT有助於減少服務器請求數據庫的次數,請求數據庫的一次網絡往返時間(通過數據庫查詢session信息),相對比HMAC SHA256計算Token驗證和解析要費時得多。

 

3 JWT問題和趨勢

令牌問題:JWT默認不加密,但可以加密。生成原始令牌後,可以使用該令牌再次對其進行加密。

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

安全問題:JWT本身包含認證信息,因此一旦信息泄露,任何人都可以獲得令牌的所有權限。爲了減少盜用,JWT的有效期不宜設置太長。對於某些重要操作,用戶在使用時應該每次都進行進行身份驗證。而且JWT不建議使用HTTP協議來傳輸代碼,爲了減少盜用和竊取,而是使用加密的HTTPS協議進行傳輸。

 

4 JWT的組成

JSON Web令牌以緊湊的形式由三部分組成,這些部分由點(.)分隔,分別是:

  • 頭部
  • 有效載荷
  • 簽名

因此,JWT通常如下結構所示。

xxxxx.yyyyy.zzzzz

 

頭部

JWT頭部分是一個描述JWT元數據的JSON對象,通常如下所示。

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

alg屬性表示簽名使用的算法,默認爲HMAC SHA256(寫爲HS256);typ屬性表示令牌的類型,JWT令牌統一寫爲JWT。

最後,使用Base64 URL算法將上述JSON對象轉換爲字符串保存,。

然後,此JSON被Base64 Url編碼以形成JWT的第一部分。

 

有效載荷

有效載荷部分,是JWT的主體內容部分,也是一個JSON對象,包含需要傳遞的數據。 JWT指定七個默認字段供選擇

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

除以上默認字段外,我們還可以自定義私有字段,如下例:

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

 

然後,對有效負載進行Base64Url編碼,以形成JSON Web令牌的第二部分。

注意:

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

    對於已簽名的令牌,此信息儘管可以防止篡改,但任何人都可以讀取。除非將其加密,否則請勿將重要信息放入JWT的有效負載或報頭元素中。

 

簽名

簽名部分是對上面兩部分數據簽名,通過指定的算法生成哈希,以確保數據不會被篡改

我們需要指定一個密鑰(secret)。該密碼僅在服務器中使用,不能公開。然後,使用標頭中指定的簽名算法(默認情況下爲HMAC SHA256)根據以下公式生成簽名

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

注意:

    簽名主要通過密鑰進行加密解密,用於驗證JWT在整個過程中有沒有被更改,保證JWT的完整性。

    作爲令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三個字符是"+","/"和"=",由於在URL中有特殊含義,因此Base64URL中對他們做了替換:"="去掉,"+"用"-"替換,"/"用"_"替換,在使用的時候需要注意。

 

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

ç¼ç çJWT

JWT解析地址:https://jwt.io/#libraries

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

通過解析上面JWT,可以得到以下圖中標頭,載荷信息:

 

4.java中使用

在項目中建議使用JJWT,因爲其API相對友好

引入Maven依賴

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

在項目中新建JwtDemo類

在jwtDemo類中添加以下jwt常量

public class JwtDemo {

    //加密算法
    private final static SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;

    //私鑰 / 生成簽名的時候使用的祕鑰secret,一般可以從本地配置文件中讀取,切記這個祕鑰不能外露,只在服務端使用,在任何場景都不應該流露出去。
    // 一旦客戶端得知這個secret, 那就意味着客戶端是可以自我簽發jwt了。
    private final static String secret = "secretKey";

    // 過期時間(單位秒)/ 2小時
    private final static Long access_token_expiration = 7200L;

    //jwt簽發者
    private final static String jwt_iss = "spzhang";

    //jwt所有人
    private final static String subject = "zhangsp";

}

JWT生成方法

在jwtDemo類下添加以下jwt生成方法

 /**
     * 創建jwt
     * @return
     *      返回生成的jwt token
     */
    public static String generateJwtToken(){

        // 頭部 map / Jwt的頭部承載,第一部分
        // 可不設置 默認格式是{"alg":"HS256"}
        Map<String, Object> map = new HashMap<>();
            map.put("alg", "HS256");
            map.put("typ", "JWT");


        //載荷 map / Jwt的載荷,第二部分
        Map<String,Object> claims = new HashMap<String,Object>();

            //私有聲明 / 自定義數據,根據業務需要添加
            claims.put("id","123456");
            claims.put("userName", "admin");

            //標準中註冊的聲明 (建議但不強制使用)
            //一旦寫標準聲明賦值之後,就會覆蓋了那些標準的聲明
            claims.put("iss", jwt_iss);
            /*	iss: jwt簽發者
                sub: jwt所面向的用戶
                aud: 接收jwt的一方
                exp: jwt的過期時間,這個過期時間必須要大於簽發時間
                nbf: 定義在什麼時間之前,該jwt都是不可用的.
                iat: jwt的簽發時間
                jti: jwt的唯一身份標識,主要用來作爲一次性token,從而回避重放攻擊
            */


        //下面就是在爲payload添加各種標準聲明和私有聲明瞭
        return Jwts.builder() // 這裏其實就是new一個JwtBuilder,設置jwt的body
                .setHeader(map)         // 頭部信息
                .setClaims(claims)      // 載荷信息
                .setId(UUID.randomUUID().toString()) // 設置jti(JWT ID):是JWT的唯一標識,從而回避重放攻擊。
                .setIssuedAt(new Date())       // 設置iat: jwt的簽發時間
                .setExpiration(new Date(System.currentTimeMillis() + access_token_expiration * 1000)) // 設置exp:jwt過期時間
                .setSubject(subject)    //設置sub:代表這個jwt所面向的用戶,所有人
                .signWith(SIGNATURE_ALGORITHM, secret)//設置簽名:通過簽名算法和祕鑰生成簽名
                .compact(); // 開始壓縮爲xxxxx.yyyyy.zzzzz 格式的jwt token
    }

 

JWT解析方法

在jwtDemo下添加以下解析方法

 /**
     *  從jwt中獲取 載荷 信息
     * @param jwt
     * @return
     */
    private static Claims getClaimsFromJwt(String jwt) {
        Claims claims = null;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(jwt).getBody();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return claims;
    }

 

測試方法

通過Jwt生成Jwt token,並通過解析jwt獲取 載荷 信息

    public static void main(String[] args) {
        String jwtToken = generateJwtToken();
        System.out.println("JWT Token "+ jwtToken);
        System.out.println("=======================================================");

        Claims claims = getClaimsFromJwt(jwtToken);
        System.out.println(claims);
        System.out.println(claims.get("userName"));
    }

通過執行得到了以下信息,可以看到生成時間,和失效時間,自定義數據等

JWT Token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ6aGFuZ3NwIiwiaXNzIjoic3B6aGFuZyIsImlkIjoiMTIzNDU2IiwidXNlck5hbWUiOiJhZG1pbiIsImV4cCI6MTU3NjEyODQzMSwiaWF0IjoxNTc2MTIxMjMxLCJqdGkiOiI1YWM1NTYyMy03MGMyLTRkYzgtYThkMC1lMGFlY2I5OTNmMTQifQ.51y0YnmPvDJBzzMe9aNwkZG6Pgcvqq6vJHtaTuY1CEI
=======================================================
{sub=zhangsp, iss=spzhang, id=123456, userName=admin, exp=1576128431, iat=1576121231, jti=5ac55623-70c2-4dc8-a8d0-e0aecb993f14}
admin

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章