[JWT][JAVA]JSON Web Tokens的簡單使用(java-jwt)


前導

前一篇中介紹了Token的機制以及一個簡單實現了一個Token管理類。
這篇中介紹的是現有的工業化框架—— JWT (JSON Web Tokens)。

JWT簡介

什麼是JWT

概括來說,JWT是一種符合RFC 7519工業體系設計的、基於JSON對象的、利用RSA等算法簽發的Token框架/標準。

  • JWT在通常情況下,並不對Token數據進行加密(和上一篇中簡單利用數字摘要技術產生的Token不同,JWT簽發的Token是可以進行逆向解碼的),只關注於Token令牌本身的簽發時效性、完整性、合法性。
  • JWT同樣可以利用加密手段對Token進行簽發。但通常來說並不推薦這麼做,相比於利用HTTP傳遞一個經過JWT加密的Token,更好的做法是利用HTTPS傳遞一個Token。同時也不建議在JWT簽發的Token中傳遞敏感信息——但合理的利用加密Token傳遞數據可以有效減輕服務器負載。

對於JWT,它的通用環境主要有兩種:

  • 認證:廣泛應用於分佈式集羣網絡、跨域訪問和單頁面應用等。
  • 信息交換:儘管這不是個很安全的做法,但合理利用JWT,基於數據層安全協議可以有效減輕服務器頻繁讀取所造成的負載。

注:官方對此有更詳細的說明:

  • Authorization: This is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token. Single Sign On is a feature that widely uses JWT nowadays, because of its small overhead and its ability to be easily used across different domains.
  • Information Exchange: JSON Web Tokens are a good way of securely transmitting information between parties. Because JWTs can be signed—for example, using public/private key pairs—you can be sure the senders are who they say they are. Additionally, as the signature is calculated using the header and the payload, you can also verify that the content hasn’t been tampered with.

跨語言的特點

JWT是一套框架和標準,其實現含有JavaPythonC#Node.js,每種語言也有不同的實現庫。因此,基於JWT可以實現跨語言的Token簽發、認證、解碼。

Ref: JWT.IO-Libraries for Token Signing/Verification

主體結構

JWT的主體結構爲:

  • Header
  • Payload
  • Signature

習慣上分別將之稱作頭部、載荷、簽名。

大部分時候,我們在代碼中只關心頭部中的加密算法和載荷中的信息。

1 頭部(Header)

頭部主要包含兩類信息,加密算法(alg)和令牌簽發類型(typ),一個示例如下:

{
  "alg": "HS256",
  "typ": "JWT"
}
  • 注意:出於簡潔和數據傳輸時的開銷考慮,每個JSON字符串的索引都是三個字符的縮寫,如alg(algorithm)。

2 載荷(Payload)

  • 載荷是我們在編程中需要着重關注和實現的地方。

載荷包含三種聲明域(Claims):

  1. 簽發域(Registered claims)

Ref:簽發域標準——RFC 7519:Registered Claim Names

簽發域是我們需要着重關注和了解的第一個域。其中重中之重主要有四個:iss(issuer),exp(expiration time),sub(subject),aud(audience)。但此處將會把每一個都羅列:

縮寫 子域名 說明
iss Issuer *(必需項) 簽發此Token的發行者,如 CSDN
sub Subject 標識主體,用於識別用戶的唯一身份標識,如 張三
aud Audience 接受此JWT簽發Token的受衆,如 Chrome用戶
exp Expiration Time Token的過期時間,詳見下
nbf Not Before Token的啓用時間,詳見下
iat Issued At Token的簽發時間,詳見下
jti JWT ID 爲此JWT受以一個身身份標識符,主要用於多JWT簽發時的Token唯一性,如 001

注意:上表中帶*的是必需項,其餘是可選項。

關於三個Token的時間expnbf, iat,記收到Token的時間爲TODAY,有以下產生Token認證失敗的原因:

  • 簽發時間非法:iat < TODAY
  • 令牌過期:exp < TODAY
  • 令牌未到啓用時間:nbf < TODAY

優先接獲異常的順序時自上到下。

Tips:如果這三項未指定則不會觸發相應的校驗規則。

  1. 公共域(Public claims)

Ref:公共域標準——RFC 7519:Public Claim Names

公共域信息是非加密的信息,服務器可以在這裏捎帶一些非敏感的數據信息,例如用戶頭像的URL等。
使用樣例可見文末的java-jwt使用樣例。

  1. 私有域(Private claims)

Ref:私有域標準——RFC 7519:Private Claim Names

私有域的信息用於創建共享信息。在編程中常與共有域一同視爲一個超集——自定義域。

注意:雖然名字爲私有域,但其並不進行加密。

3 簽名(Signature)

  • 在編程中,我們並不需要關注簽名是如何形成和驗證的,但瞭解簽名的組成,對理解JWT的框架標準有一定作用。

簽名是對頭部、載荷利用先前指定的加密算法進行的簽發,用以證明這個Token確實是服務器發出的。簽發時,將會利用服務器的私鑰secret進行簽發,因此簽名部分的形成算法如下(假定使用HMACSHA256算法進行加密):

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

其中,base64UrlEncode是對base64算法的轉義,主要是替換掉=-等在URL中具有特殊意義的字符。


JWT實踐(Java)

JWT的實現

JWT是一套框架和標準,其實現含有JavaPythonC#Node.js,每種語言也有不同的實現庫。
這裏主要使用 java-jwt@Github,這也是官方的實現庫,目前實現版本爲3.7.0,可以使用Maven或Gradle等進行配置。

  • Maven
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.7.0</version>
</dependency>
  • Gradle
implementation 'com.auth0:java-jwt:3.7.0'

或者到我備份的CSDN地址下載jar包:https://download.csdn.net/download/shenpibaipao/11015001

java-jwt樣例代碼

public static void main(String [] args){
    // 設置一個私鑰,也可以使用KeyProvider產生,參見:
    // @link https://github.com/auth0/java-jwt#using-a-keyprovider
    String key = "Shenpibaipao";
    // 給定一個算法,如HmacSHA-256
    Algorithm alg = Algorithm.HMAC256(key);

    // 1 簽發Token
    Date currentTime = new Date();
    String token = JWT.create()
            .withIssuer("CSDN Blog") // 發行者
            .withSubject("userid") // 用戶身份標識
            .withAudience("CSDN User") // 用戶單位
            .withIssuedAt(currentTime) // 簽發時間
            .withExpiresAt(new Date(currentTime.getTime() + 24*3600*1000L)) // 一天有效期
            .withJWTId("001") // 分配JWT的ID
            .withClaim("PublicClaimExample", "You should not pass!") // 定義公共域信息
            .sign(alg);

    System.out.println("生成的Token是:"+token);

    // 2 驗證Token
    JWTVerifier verifier = JWT.require(alg)
            .withIssuer("CSDN Blog")
            .withAudience("CSDN User")
            .build();
    try{
        verifier.verify(token);
        System.out.println("驗證通過!");
    } catch (JWTVerificationException e) {
        e.printStackTrace();
        System.out.println("驗證失敗!");
    }

    // 3 嘗試解碼
    try{
        DecodedJWT originToken = JWT.decode(token);
        System.out.println("解碼得到發行者是:"+originToken.getIssuer());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("解碼得到簽發時間是:"+sdf.format(originToken.getIssuedAt()));
        System.out.println("解碼得到公共域信息是:"+originToken.getClaim("PublicClaimExample").asString());
    } catch (JWTDecodeException e){
        e.printStackTrace();
    }

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