JWT學習(二):Json Web Token JWT的Java使用 (JJWT)

什麼是JWT?

Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。java

jwt的組成git

  • Header: 標題包含了令牌的元數據,而且在最小包含簽名和/或加密算法的類型
  • Claims: Claims包含您想要簽署的任何信息
  • JSON Web Signature (JWS): 在header中指定的使用該算法的數字簽名和聲明

示例:github

1
2
3
4
5
6
7
8
9
10
11
12
13
Header:
{
  "alg": "HS256",
  "typ": "JWT"
}
Claims:
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
Signature:
base64UrlEncode(Header) + "." + base64UrlEncode(Claims)

加密生成的token:web

有關JWT的詳細介紹,請閱讀這篇文章:JWT學習(一):什麼是JWT?- JSON WEB TOKEN算法

JJWT

JJWT是一個提供端到端的JWT建立和驗證的Java庫。永遠免費和開源(Apache License,版本2.0),JJWT很容易使用和理解。它被設計成一個以建築爲中心的流暢界面,隱藏了它的大部分複雜性。apache

  • JJWT的目標是最容易使用和理解用於在JVM上建立和驗證JSON Web令牌(JWTs)的庫。
  • JJWT是基於JWT、JWS、JWE、JWK和JWA RFC規範的Java實現。
  • JJWT還添加了一些不屬於規範的便利擴展,好比JWT壓縮和索賠強制。

JJWT 規範兼容

  • 建立和解析明文壓縮JWTs
  • 建立、解析和驗證全部標準JWS算法的數字簽名壓縮JWTs(又稱JWSs):
  • HS256:使用SHA-256的HMAC
  • HS384:使用SHA-384的HMAC
  • HS512:使用SHA-512的HMAC
  • RS256:使用SHA-256的RSASSA-PKCS-v1_5
  • RS384:使用SHA-384的RSASSA-PKCS-v1_5
  • RS512:使用SHA-512的RSASSA-PKCS-v1_5
  • PS256:使用SHA-256的RSASSA-PSS和使用SHA-256的MGF1
  • PS384:使用SHA-384的RSASSA-PSS和使用SHA-384的MGF1
  • PS512:使用SHA-512的RSASSA-PSS和使用SHA-512的MGF1
  • ES256:使用P-256和SHA-256的ECDSA
  • ES384:使用P-384和SHA-384的ECDSA
  • ES512:使用P-521和SHA-512的ECDSA

下面咱們根據 https://github.com/jwtk/jjwt 上的demo,來介紹下 JJWT 的用法。json

jjwt 安裝

jjwt 提供了 Maven 和 Gradle 兩種構建方式,Maven 配置以下便可使用 JJWT。後端

1
2
3
4
5
<dependency>     
	<groupId>io.jsonwebtoken</groupId>     
	<artifactId>jjwt</artifactId>     
	<version>0.9.0</version>
</dependency>

Gradle 使用方式以下:api

1
dependencies { compile 'io.jsonwebtoken:jjwt:0.9.0' }

注意:JJWT依賴於Jackson 2.x. 若是您已經在您的應用程序中使用了舊版本的 Jackson 請升級相關配置。數組

示例代碼

簽發JWT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static String createJWT() {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        JwtBuilder builder = Jwts.builder()
                .setId(id)                                      // JWT_ID
                .setAudience("")                                // 接受者
                .setClaims(null)                                // 自定義屬性
                .setSubject("")                                 // 主題
                .setIssuer("")                                  // 簽發者
                .setIssuedAt(new Date())                        // 簽發時間
                .setNotBefore(new Date())                       // 失效時間
                .setExpiration(long)                                // 過時時間
                .signWith(signatureAlgorithm, secretKey);           // 簽名算法以及密匙
        return builder.compact();
}

驗證JWT

1
2
3
4
5
6
7
public static Claims parseJWT(String jwt) throws Exception {
    SecretKey secretKey = generalKey();
    return Jwts.parser()
        .setSigningKey(secretKey)
        .parseClaimsJws(jwt)
        .getBody();
}

完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import com.google.gson.Gson;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtil {

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public SecretKey generalKey() {
        String stringKey = Constant.JWT_SECRET;

        // 本地的密碼解碼
        byte[] encodedKey = Base64.decodeBase64(stringKey);

        // 根據給定的字節數組使用AES加密算法構造一個密鑰
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");

        return key;
    }

    /**
     * 建立jwt
     * @param id
     * @param issuer
     * @param subject
     * @param ttlMillis
     * @return
     * @throws Exception
     */
    public String createJWT(String id, String issuer, String subject, long ttlMillis) throws Exception {

        // 指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經將這部份內容封裝好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的時間
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        // 建立payload的私有聲明(根據特定的業務須要添加,若是要拿這個作驗證,通常是須要和jwt的接收方提早溝通好驗證方式的)
        Map<String, Object> claims = new HashMap<>();
        claims.put("uid", "123456");
        claims.put("user_name", "admin");
        claims.put("nick_name", "X-rapido");

        // 生成簽名的時候使用的祕鑰secret,切記這個祕鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不該該流露出去。
        // 一旦客戶端得知這個secret, 那就意味着客戶端是能夠自我簽發jwt了。
        SecretKey key = generalKey();

        // 下面就是在爲payload添加各類標準聲明和私有聲明瞭
        JwtBuilder builder = Jwts.builder() // 這裏其實就是new一個JwtBuilder,設置jwt的body
                .setClaims(claims)          // 若是有私有聲明,必定要先設置這個本身建立的私有的聲明,這個是給builder的claim賦值,一旦寫在標準的聲明賦值以後,就是覆蓋了那些標準的聲明的
                .setId(id)                  // 設置jti(JWT ID):是JWT的惟一標識,根據業務須要,這個能夠設置爲一個不重複的值,主要用來做爲一次性token,從而回避重放攻擊。
                .setIssuedAt(now)           // iat: jwt的簽發時間
                .setIssuer(issuer)          // issuer:jwt簽發人
                .setSubject(subject)        // sub(Subject):表明這個JWT的主體,即它的全部人,這個是一個json格式的字符串,能夠存放什麼userid,roldid之類的,做爲何用戶的惟一標誌。
                .signWith(signatureAlgorithm, key); // 設置簽名使用的簽名算法和簽名使用的祕鑰

        // 設置過時時間
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }
        return builder.compact();
    }

    /**
     * 解密jwt
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public Claims parseJWT(String jwt) throws Exception {
        SecretKey key = generalKey();  //簽名祕鑰,和生成的簽名的祕鑰如出一轍
        Claims claims = Jwts.parser()  //獲得DefaultJwtParser
                .setSigningKey(key)                 //設置簽名的祕鑰
                .parseClaimsJws(jwt).getBody();     //設置須要解析的jwt
        return claims;
    }

    public static void main(String[] args) {

        User user = new User("tingfeng", "bulingbuling", "1056856191");
        String subject = new Gson().toJson(user);

        try {
            JwtUtil util = new JwtUtil();
            String jwt = util.createJWT(Constant.JWT_ID, "Anson", subject, Constant.JWT_TTL);
            System.out.println("JWT:" + jwt);

            System.out.println("\n解密\n");

            Claims c = util.parseJWT(jwt);
            System.out.println(c.getId());
            System.out.println(c.getIssuedAt());
            System.out.println(c.getSubject());
            System.out.println(c.getIssuer());
            System.out.println(c.get("uid", String.class));

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

常量類:Constant.java

1
2
3
4
5
6
7
8
9
10
11
import java.util.UUID;

public class Constant {
    public static final String JWT_ID = UUID.randomUUID().toString();

    /**
     * 加密密文
     */
    public static final String JWT_SECRET = "woyebuzhidaoxiediansha";
    public static final int JWT_TTL = 60*60*1000;  //millisecond
}

輸出示例:

1
2
3
4
5
6
7
8
9
JWT:eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiIxMjM0NTYiLCJzdWIiOiJ7XCJuaWNrbmFtZVwiOlwidGluZ2ZlbmdcIixcIndlY2hhdFwiOlwiYnVsaW5nYnVsaW5nXCIsXCJxcVwiOlwiMTA1Njg1NjE5MVwifSIsInVzZXJfbmFtZSI6ImFkbWluIiwibmlja19uYW1lIjoiWC1yYXBpZG8iLCJpc3MiOiJBbnNvbiIsImV4cCI6MTUyMjMxNDEyNCwiaWF0IjoxNTIyMzEwNTI0LCJqdGkiOiJhNGQ5MjA0Zi1kYjM3LTRhZGYtODE0NS1iZGNmMDAzMzFmZjYifQ.B5wdY3_W4MZLj9uBHSYalG6vmYwdpdTXg0otdwTmU4U

解密

a4d9204f-db37-4adf-8145-bdcf00331ff6
Thu Mar 29 16:02:04 CST 2018
{"nickname":"tingfeng","wechat":"bulingbuling","qq":"1056856191"}
Anson
123456

通常咱們把驗證操做做爲中間件或者攔截器就好了,請求後端API接口時候,經過過濾器獲取header中的token,驗證是否正確、是否過時等。

相關文章
相關標籤/搜索