天天看點

【JWT】JWT 實作使用者登入控制一、JWT 簡介二、JWT 的構成三、工具類代碼

目錄

一、JWT 簡介

二、JWT 的構成

2.1 頭部(Header)

2.2 載荷 (Payload)

2.3 簽名

2.4 最後生成出的 jwt 

2.5 測試和驗證

三、工具類代碼

一、JWT 簡介

JSON Web Token(JWT) 是一種十分輕巧的規範,這個規範允許我們使用 JWT 在使用者和伺服器之間傳遞安全可靠的資訊。

即 JWT 是用于在用戶端和伺服器之間傳遞使用者資訊的一段 JSON 格式的加密字元串,同時 JWT 可以用于讓各個微服務識别使用者的身份資訊。即 JWT 中封裝有使用者的身份資訊。

二、JWT 的構成

一個 JWT 實際上就是一個字元串,它由三部分組成:頭部、載荷和簽名

2.1 頭部(Header)

頭部用于描述關于該 JWT 的最基本資訊,例如它的類型以及簽名所用的算法等,這一部分被可以被表示成一個 JSON 對象。

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

這段 JSON 字元串指明了簽名算法是 HS256 算法,實際使用中這段字元串會被 base64編碼,被編碼後的字元串為:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
           

轉換位址:http://base64.xpcha.com/

2.2 載荷 (Payload)

載荷就是存放有效資訊的地方

假設真實内容為:

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

base64 加密後:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
           

标準中的注冊聲明(建議但不強制使用):

聲明名稱 作用
iss jwt 的簽發者
sub 目前令牌的描述說明
aud 接收 jwt 的一方
exp jwt 的過期時間,這個時間必須大于簽發時間
nbf 定義在什麼時間之前,這個 jwt 是不可用的
iat jwt 的簽發時間
jti jwt 的唯一身份辨別,主要用來作為一次性 token,避免重播攻擊

公共聲明:

公共的聲明可以添加任何資訊,一般添加使用者的相關資訊或其他業務需要的必要資訊,但由于這部分資訊在用戶端可以被解密,設定這部分可以不加密,故不建議添加敏感資訊。這部分資訊不參與令牌校驗。

私有聲明:

私有聲明是提供者和消費者共同定義的聲明,由于 base64是對稱解密,即這部分資訊可以被解密,故不建議存放敏感資訊。這部分資訊不參與令牌校驗。

一個載荷的構成:

(标準中注冊的聲明 + 公共的聲明 + 私有聲明 ) => base64 加密 => 一個載荷

2.3 簽名

jwt 的第三部分是一個簽證資訊,用于校驗資料是否被篡改。簽證資訊由三部分組成:

  • header(base64後的)
  • payload(base64後的)
  • secret (秘鑰,或稱鹽)

簽名的生成方式是:

Base64(Header) . Base64(Payload) + 秘鑰(鹽) => 加密(使用 Header 中指定的算法加密) => 密文(簽名)

例如(資料不準确):

Base64(Header)為:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Base64(Payload)為:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

secret為: abcdefg

則簽名為: HS256(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.abcdefg) => TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:

secret 是儲存在伺服器端的, jwt 的簽發也是發生在伺服器端,可以認為 secret 就是伺服器端用于簽名的私鑰,在任何場景中都不應該洩漏出去,一旦用戶端得知這個 secret,就意味着用戶端可以自我簽發 jwt 了。

簽名的作用:

用于校驗令牌是否被篡改

由于 secret 是儲存在伺服器的,若認為篡改了 jwt,則簽名部分肯定發生了變化,伺服器隻要對比簽名部分就能夠判斷出 jwt 是否被篡改。

2.4 最後生成出的 jwt 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7Hg

Q

2.5 測試和驗證

/**
 * @File: JwtTest
 * @Description: 令牌生成和解析測試
 * @Author: tom
 **/
@Slf4j
public class JwtTest {

    private String signKey = "tomTest";

    /**
     * 建立令牌
     */
    @Test
    public void testCreateToken() throws InterruptedException {
        // 建構jwt令牌對象
        JwtBuilder builder = Jwts.builder();
        builder.setIssuer("tom"); // 頒發者
        builder.setIssuedAt(new Date()); // 頒發日期
        builder.setExpiration(new Date(System.currentTimeMillis() + 20000)); // 設定定時,2秒過期
        builder.setSubject("jwt 令牌測試"); // 主題測試
        builder.signWith(SignatureAlgorithm.HS256, signKey); // 1. 簽名算法 2. 秘鑰
        // 自定義載荷資訊
        Map<String, Object> userInfo = new HashMap<String,Object>();
        userInfo.put("company", "dis");
        userInfo.put("address", "北京");
        userInfo.put("money", 3500);
        // 添加載荷
        builder.addClaims(userInfo);

        // 擷取生成 token
        String token = builder.compact();
        System.out.println(token);

//        Thread.sleep(3000);
        parseToken(token);
    }

    /**
     * 令牌解析
     */
    public void parseToken(String token) {
        Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(token).getBody();
        System.out.println(claims.toString());
    }
}
           

三、工具類代碼

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;

/**
 * JWT工具類
 */
public class JwtUtil {

    // 有效期為
    public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000  一個小時
    // 設定秘鑰明文
    public static final String JWT_KEY = "tomcast";
    // 設定頒發者
    public static final String JWT_ISS = "tom";

    /**
     * 建立token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {

        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        SecretKey secretKey = generalKey();

        JwtBuilder builder = Jwts.builder()
                .setId(id)              //唯一的ID
                .setSubject(subject)   // 主題  可以是JSON資料
                .setIssuer(JWT_ISS)     // 簽發者
                .setIssuedAt(now)      // 簽發時間
                .signWith(signatureAlgorithm, secretKey) //使用HS256對稱加密算法簽名, 第二個參數為秘鑰
                .setExpiration(expDate);// 設定過期時間
        return builder.compact();
    }

    /**
     * 生成加密後的秘鑰 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 解析令牌資料
     * @param jwt
     * @return
     */
    public static Claims parseJWT(String jwt) {
        SecretKey secretKey = generalKey();
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
    }
}