web-security第四期:JWT的 java 實現——jjwt

目錄

1.JWT簡介

2.token的結構

2.1.Header

2.2.Payload

2.3.Signature(簽名)

2.4.將上面部分合體

3.Token工作方式

4.JWT的Java實現

4.1.創建JWS

4.2.讀取JWS


下一期:spring-security + JWT 整合

1.JWT簡介

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

有兩個主要應用場景:

  • 授權:這是使用JWT的最常見方案。一旦用戶登錄,每個後續請求將包括JWT(加密過的用戶信息),從而允許用戶訪問該令牌允許的路由,服務和資源。單一登錄是當今廣泛使用JWT的一項功能,因爲它的開銷很小並且可以在不同的域中輕鬆使用。
  • 信息交換:JWT是在各方之間安全地傳輸信息的好方法。因爲可以對JWT進行簽名(例如,使用公鑰/私鑰對),所以您可以確定發件人是他們所說的人。另外,由於簽名是使用標頭和有效負載計算的,因此您還可以驗證內容是否未被篡改

承接上一期的內容,這裏我們只探討它在認證授權方面的應用

2.token的結構

JSON Web Token由下面三個部分組成,每個部分使用 分割開來

  • Header
  • Payload
  • Signature

整體結果類似於

hhhhhh.ppppppp.sssssss

2.1.Header

標頭通常由兩部分組成:令牌的類型(即JWT)和所使用的Signature(簽名)算法,例如HMAC SHA256或RSA。如下:

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

然後,此JSON被Base64Url編碼爲JWT的第一部分(Header)。

2.2.Payload

這是令牌主題信息的承載部分,包含三個信息聲明(Claims:Claims are statements about an entity (typically, the user) and additional data)

  • 登記信息聲明(Registered claims):這部分包含一些聲明信息,不是強制性的,但是推薦,如:(注意:JWT規定聲明的信息名長度爲三個字符,因爲JWT信息較爲緊湊)
    • iss(發佈者)
    • exp(到期時間)
    • sub(主體)
    • aud(接受者)
  • 公開聲明信息(Public claims):這些可以由使用JWT的人員隨意定義。但是爲避免衝突,應在IANA JSON Web令牌註冊表中定義它們,或將其定義爲包含抗衝突名稱空間的URI
  • 私有聲明信息(Private claims):這些是自定義聲明,旨在在同意使用它們的各方之間共享信息,既不是註冊聲明也不是公共聲明。

類似於如下結構:

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

然後,此JSON被Base64Url編碼爲JWT的第二部分

2.3.Signature(簽名)

使用Header中定義的編碼方式,和前面定義的信息,我們可以以如下方式創建簽名:

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

簽名用於驗證消息在整個過程中沒有更改,並且對於使用私鑰進行簽名的令牌,它還可以驗證JWT的發送者是它所說的真實身份。

2.4.將上面部分合體

由上面的分析可知,JWT的輸出是三個由點分隔的Base64-URL字符串,可以在HTML和HTTP環境中輕鬆傳遞

 

3.Token工作方式

在身份驗證中,當用戶使用其憑據成功登錄時,將返回JSON Web令牌。由於令牌是憑據,因此必須格外小心以防止安全問題。通常,令牌的保留時間不應超過要求的時間。

由於缺乏安全性,您也不應該將敏感的會話數據存儲在瀏覽器中。

每當用戶想要訪問受保護的路由或資源時,用戶代理通常應在Bearer模式中使用授權頭髮送JWT 。標頭的內容應如下所示:

Authorization: Bearer <token>

在某些情況下,這可以是無狀態授權機制。服務器的受保護路由將在Authorization標頭中檢查有效的JWT ,如果存在,則將允許用戶訪問受保護的資源。如果JWT包含必要的數據(例如用戶的個人信息),則可以減少查詢數據庫中某些操作的需求,儘管這種情況並非總是如此。

如果令牌是在Authorization標頭中發送的,則跨域資源共享(CORS)不會成爲問題,因爲它不使用cookie(Session也基於Cokkie)。

 

4.JWT的Java實現

JWT是一種標準,而它的實現有很多種,幾乎涵蓋大部分編程語言,這裏我們使用Java的一種實現:jjwt

github項目地址:鏈接

這裏先明白幾個名詞:

  • JSON Web Signature (JWS) (簽名 A signed JWT is called a 'JWS')
  • JSON Web Encryption (JWE) (加密)
  • JSON Web Key (JWK) (密鑰)

我們要使用的是一個被簽名的JWT ,即JWS,我們先來創建一個JWS:

4.1.創建JWS

創建一個SecretKey (密鑰)

SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

jjwt提供的密鑰加密方式有很多種,自行選擇,常用HS256

定義也就是設置上面我們分析的那些值:

public static void main(String[] args) {
        //密鑰
        SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        String jws = Jwts
                .builder()
                //主題信息,即前面分析的Header和Payload(Claims)

                //設置一個頭部信息
                .setHeaderParam("kid", "myKeyId")
                //也可設置多個頭部信息
//                .setHeader(new HashMap<>(3))

                //或多個Claims
//                .setClaims(new HashMap<String, Object>(3))


                //標準的Claims
                //令牌發出者
                .setIssuer("me")
                // 發出時間
                .setIssuedAt(new Date())
                //主體信息
                .setSubject("Bob")
                //令牌接受者
                .setAudience("you")
                //失效日期
                .setExpiration(new Date(System.currentTimeMillis() + 3000))
                //a java.util.Date
                .setNotBefore(new Date(System.currentTimeMillis() + 2000))
                //just an example id
                .setId(String.valueOf(UUID.randomUUID()))

                //自定義Claims
                .claim("hello", "world")
                
                //指定密鑰
                .signWith(key)
                //調用compact()方法進行壓縮和簽名,生成最終的jws
                .compact();
        System.out.println(jws);
    }


結果:eyJraWQiOiJteUtleUlkIiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiJtZSIsImlhdCI6MTU5MTg2Mjc1OCwic3ViIjoiQm9iIiwiYXVkIjoieW91IiwiZXhwIjoxNTkxODYyNzYxLCJuYmYiOjE1OTE4NjI3NjAsImp0aSI6IjgyMDJjMWQ4LWFlMzQtNGEwZS1hZjFkLWM0OTY0MTZhZjhlZiIsImhlbGxvIjoid29ybGQifQ.y4FMmiuxQ5huH2h_gYkT5awUiay1LO5kxt4GRNJlEnc

注意:.setClaims() 一定要在標準的Claims之前使用,否則會覆蓋標準的Claims 

4.2.讀取JWS

 直接給出代碼:

public static void main(String[] args) {
        SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        String token = JwtService.getToken(key);
        System.out.println(token);
        Jws<Claims> jws;
        jws = Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token);
        System.out.println(jws.getHeader());
        System.out.println(jws.getBody());
        System.out.println(jws.getSignature());
    }


result:

eyJraWQiOiJteUtleUlkIiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiJtZSIsImlhdCI6MTU5MTg2MzY1MSwic3ViIjoiQm9iIiwiYXVkIjoieW91IiwiZXhwIjoxNTkxODYzNjU0LCJqdGkiOiIyNGZiNDFkMy1jY2UyLTQzZDMtYjMyOC0xYzBlNmYzYjg1Y2YiLCJoZWxsbyI6IndvcmxkIn0.OI2YnQlVm-VNTgk_SRt2V6yhWoc0RqIIl11XcBaSopo
{kid=myKeyId, alg=HS256}
{iss=me, iat=1591863651, sub=Bob, aud=you, exp=1591863654, jti=24fb41d3-cce2-43d3-b328-1c0e6f3b85cf, hello=world}
OI2YnQlVm-VNTgk_SRt2V6yhWoc0RqIIl11XcBaSopo

可見,Header 和 Payload 都是以 Map格式存儲的

還要注意一點,這裏的加密和解密的 SecretKey 必須是同一個, 即使是相同算法的的兩個也不行

下一期我們就來使用 jjwt 完成Spring Security 的記住我的功能

 

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