1. 程式人生 > 程式設計 >玩轉 SpringBoot 2 之整合 JWT 上篇

玩轉 SpringBoot 2 之整合 JWT 上篇

前言

該文主要帶你瞭解什麼是 JWT,以及JWT 定義和先關概念的介紹,並通過簡單Demo 帶你瞭解如何使用 SpringBoot 2 整合 JWT。介紹前在這裡我們來探討一下如何學習一門新的技術,我個人總結為 RSA

  1. R:read 去讀官方檔案 。
  2. S:search 谷歌或百度先關技術文章或 github 去搜索先關資訊。
  3. A:ask 可以向技術大牛請教或和自己的同事同學進行探討。

關於 RSA 僅僅程式碼個人的學習觀點,只是給讀者一個不成熟的小建議哈

JWT 介紹

官網介紹如下:

What is JSON Web Token?

JSON Web Token (JWT) is an open standard (

RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA
 or ECDSA.

Although JWTs can be encrypted to also provide secrecy between parties,we will focus on signed tokens. Signed tokens can verify the integrity of the claims contained within it,while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs,the signature also certifies that only the party holding the private key is the one that signed it.

中文翻譯:JSON Web令牌(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用於在各方之間作為JSON物件安全地傳輸資訊。該資訊可以被驗證和信任,因為它是數字簽名的。JWTS可以使用祕密(使用HMAC演演算法)或公鑰/私鑰對使用RSA或ECDSA來簽名。雖然JWTS可以加密,但也提供保密各方之間,我們將重點放在簽名令牌。簽名的令牌可以驗證包含在其中的宣告的完整性,而加密的令牌隱藏這些宣告以防其他各方。當令牌使用公鑰/私鑰對簽名時,簽名也證明只有持有私鑰的方才是簽名的方。

JWT 先關概念介紹

使用 JWT 前需要先了解三塊內容:

  1. 頭部資訊
  2. 載荷資訊
  3. 簽名資訊

頭部資訊

 頭部資訊由2部分組成

  1. 令牌的型別,即 JWT
  2. 使用的簽名演演算法 ,例如HMACSHA256或RSA。

頭部資訊 JSON 程式碼如下:

{
  "alg": "HS256","typ": "JWT"
}複製程式碼

然後,這個JSON被編碼為 Base64Url,形成 JWT 的第一部分。

載荷資訊

其中包含宣告(claims),宣告可以存放實體(通常是使用者)和其他資料的宣告,宣告包括3種型別

  1. 已註冊宣告
  2. 公開宣告
  3. 私有宣告

已註冊宣告這些是一組預定義宣告,不是強制性的,但建議使用,以提供一組有用的,可互操作的宣告。其中一些是: iss(發行人), exp(到期時間),sub(主題), aud(觀眾)

公開宣告可以參考 IANA JSON Web令牌登入檔https://www.iana.org/assignments/jwt/jwt.xhtml) 檢視公共的宣告

私有宣告根據根據自己的業務需要自定義的一些資料格式。示例有效負載可以是:

{

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

}複製程式碼

圖片

簽名資訊

 這個部分需要 base64 加密後的 頭部資訊(header) 和 base64 加密後的載荷資訊(payload),使用連線組成的字串,然後通過頭部資訊(header)中宣告的加密方式進行加鹽 secret 組合在加密,然後就構成了 JWT 的第三部分。例如,如果要使用HMAC SHA256演演算法,將按以下方式建立簽名:

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)複製程式碼

JWT 簡單實用演示介紹

首先去 JWT 官網一探究竟,訪問 jwt.io 進入 JWT 官網。接下來我們開始學習如果使用JWT,通過點選上圖中 LEARN MORE ABOUT JWT 顯示如下圖:然後點選START USING THE TOOL。

圖片![圖片]

圖片![圖片]

如下圖所示 選擇對應的語言的 JWT 使用教程。我們這裡介紹是是 標註有:maven:com.auth0 java-jwt 的版本。

圖片

檢視 JWT GitHub 示例教程和原始碼。 github.com/auth0/java-…

圖片

在README.md 檔案中有使用介紹和示例程式。

圖片

圖片

GitHub 上的示例相對簡單些,接下來我帶大家寫一個相對詳細的Demo。

第一步在SpringBoot 應用中引入JWT 依賴到pom.xml中

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.1</version>
</dependency>複製程式碼

我們通過三個方法來演示如何使用JWT

  1. createToken() 建立不攜帶自定義資訊的 token
  2. createTokenWithClaim() 建立攜帶自定義資訊的 token
  3. verifyToken() 驗證我們的token資訊並解析token中的內容

生成不攜帶自定義資訊 JWT token

第一步:構建頭部資訊

Map<String,Object> map = new HashMap<String,Object>();
map.put("alg","HS256");
map.put("typ","JWT");複製程式碼

第二步:構建金鑰資訊 

Algorithm algorithm = Algorithm.HMAC256("secret");複製程式碼

 第三步:我們通過定義註冊和自定義宣告 並組合頭部資訊和金鑰資訊生成jwt token

String token = JWT.create()
    .withHeader(map)// 設定頭部資訊 Header 
    .withIssuer("SERVICE")//設定 載荷 簽名是有誰生成 例如 伺服器
    .withSubject("this is test token")//設定 載荷 簽名的主題
    // .withNotBefore(new Date())//設定 載荷 定義在什麼時間之前,該jwt都是不可用的.
    .withAudience("APP")//設定 載荷 簽名的觀眾 也可以理解誰接受簽名的
    .withIssuedAt(nowDate) //設定 載荷 生成簽名的時間
    .withExpiresAt(expireDate)//設定 載荷 簽名過期的時間
    .sign(algorithm);//簽名 Signature複製程式碼

詳細程式碼如下:

    @Test
    public void createToken() {

        String secret = "secret";// token 金鑰
        Algorithm algorithm = Algorithm.HMAC256("secret");

        // 頭部資訊
        Map<String,Object>();
        map.put("alg","HS256");
        map.put("typ","JWT");

        Date nowDate = new Date();
        Date expireDate = getAfterDate(nowDate,2,0);// 2小過期
        
        String token = JWT.create()
            .withHeader(map)// 設定頭部資訊 Header 
            .withIssuer("SERVICE")//設定 載荷 簽名是有誰生成 例如 伺服器
            .withSubject("this is test token")//設定 載荷 簽名的主題
            // .withNotBefore(new Date())//設定 載荷 定義在什麼時間之前,該jwt都是不可用的.
            .withAudience("APP")//設定 載荷 簽名的觀眾 也可以理解誰接受簽名的
            .withIssuedAt(nowDate) //設定 載荷 生成簽名的時間
            .withExpiresAt(expireDate)//設定 載荷 簽名過期的時間
            .sign(algorithm);//簽名 Signature
        Assert.assertTrue(token.length() > 0);
    }複製程式碼

生成攜帶自定義資訊 JWT token

自定義資訊通過 withClaim 方法進行新增,具體操作如下:

JWT.create()
    .withHeader(map)
    .withClaim("loginName","zhuoqianmingyue")
    .withClaim("userName","張三")
    .withClaim("deptName","技術部")複製程式碼

生成攜帶自定義資訊 JWT token 詳細程式碼如下:

    @Test
    public String createTokenWithChineseClaim() {

        Date nowDate = new Date();
        Date expireDate = getAfterDate(nowDate,0);// 2小過期

        Map<String,"JWT");

        Algorithm algorithm = Algorithm.HMAC256("secret");
        String token = JWT.create().withHeader(map)
                /* 設定 載荷 Payload */
                .withClaim("loginName","zhuoqianmingyue").withClaim("userName","張三").withClaim("deptName","技術部")
                .withIssuer("SERVICE")// 簽名是有誰生成 例如 伺服器
                .withSubject("this is test token")// 簽名的主題
                // .withNotBefore(new Date())//定義在什麼時間之前,該jwt都是不可用的
                .withAudience("APP")// 簽名的觀眾 也可以理解誰接受簽名的
                .withIssuedAt(nowDate) // 生成簽名的時間
                .withExpiresAt(expireDate)// 簽名過期的時間
                /* 簽名 Signature */
                .sign(algorithm);
        
        Assert.assertTrue(token.length() > 0);
        return token;複製程式碼


驗證 JWT Token

第一步:構建金鑰資訊

 Algorithm algorithm = Algorithm.HMAC256("secret");複製程式碼

 第二步:通過金鑰資訊和簽名的釋出者的資訊生成JWTVerifier (JWT驗證類) 

JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer("SERVICE")
                .build();
 Algorithm algorithm = Algorithm.HMAC256("secret");複製程式碼

不新增 .withIssuer("SERVICE") 也是可以獲取 JWTVerifier 。

第三步:通過JWTVerifier 的verify獲取 token中的資訊。 

 DecodedJWT jwt = verifier.verify(token);複製程式碼

如下面程式碼所示就可以獲取到我們之前生成 token 的 簽名的主題,觀眾 和自定義的宣告資訊。

String subject = jwt.getSubject();
List<String> audience = jwt.getAudience();
Map<String,Claim> claims = jwt.getClaims();
for (Entry<String,Claim> entry : claims.entrySet()) {
    String key = entry.getKey();
    Claim claim = entry.getValue();
    System.out.println("key:"+key+" value:"+claim.asString());
}複製程式碼

驗證 JWT Token 詳細程式碼如下:

@Test
    public void verifyToken() throws UnsupportedEncodingException {
        String token = createTokenWithChineseClaim2();
        
        Algorithm algorithm = Algorithm.HMAC256("secret");
        JWTVerifier verifier = JWT.require(algorithm).withIssuer("SERVICE").build(); // Reusable verifier instance
        DecodedJWT jwt = verifier.verify(token);
        
        String subject = jwt.getSubject();
        List<String> audience = jwt.getAudience();
        Map<String,Claim> claims = jwt.getClaims();
        for (Entry<String,Claim> entry : claims.entrySet()) {
            String key = entry.getKey();
            Claim claim = entry.getValue();
            log.info("key:" + key + " value:" + claim.asString());
        }
        Claim claim = claims.get("loginName");

        log.info(claim.asString());
        log.info(subject);
        log.info(audience.get(0));

    }
    public String createTokenWithChineseClaim2() throws UnsupportedEncodingException {

        Date nowDate = new Date();
        Date expireDate = getAfterDate(nowDate,"JWT");

        User user = new User();
        user.setUserNaem("張三");
        user.setDeptName("技術部");
        Gson gson = new Gson();
        String userJson = gson.toJson(user);

        String userJsonBase64 = BaseEncoding.base64().encode(userJson.getBytes());

        Algorithm algorithm = Algorithm.HMAC256("secret");
        String token = JWT.create().withHeader(map)

                .withClaim("loginName","zhuoqianmingyue").withClaim("user",userJsonBase64).withIssuer("SERVICE")// 簽名是有誰生成
                .withSubject("this is test token")// 簽名的主題
                // .withNotBefore(new Date())//該jwt都是不可用的時間
                .withAudience("APP")// 簽名的觀眾 也可以理解誰接受簽名的
                .withIssuedAt(nowDate) // 生成簽名的時間
                .withExpiresAt(expireDate)// 簽名過期的時間
                .sign(algorithm);//簽名 Signature

        return token;
    }複製程式碼

小結

JWT 就是一個生成 Token 的工具,如果不使用 JWT 我們也可以根據自己加密規則生成 Token。只不過 JWT 規範了生成 Token 定義了一個標準而已。JWT 的核心的功能就是:生成Token、解析Token。在 玩轉 SpringBoot 2 之整合 JWT 下篇中將帶大家通過一個介面登入的案例簡單介紹 JWT 實戰操作。

程式碼示例

具體程式碼示例請檢視我的GitHub 倉庫 springbootexamples 中的 spring-boot-2.x-jwt 下 src/test/java JWTDemo.java檔案。

GitHub:https://github.com/zhuoqianmingyue/springbootexamples

參考文獻

https://jwt.io/introduction/https://github.com/auth0/java-jwt