JWT (b): JWT implemented in Java

JWT (a): Recognizing WebToken JSON
JWT (II): JWT implemented in Java

Introduction

Principle Part "JWT (a): Recognizing JSON Web Token" have already said, it is not difficult to achieve, you can write a jwt tools yourself (if you are interested)

Of course, repeat-create the wheel is not the programmer's style, we advocate ism!
Lu Xun - and I take that thing .jpg

JWT's official website provides multiple-language JWT library, the details can refer https://jwt.io/#debugger page bottom half

Recommended jjwt library , its github address https://github.com/jwtk/jjwt

jjwt version 0.10.7, it and 0.9.x are very different, we must pay attention! ! !

A five-part article

  • Part 1 : A simple example demonstrates generation, verification, parsing process jwt
  • Part 2 : Introduction of the commonly used methods jjwt
  • Part 3 : packaging a common jwt tools
    if only ism, seen here on it
  • Part 4 : Introduction of various signature algorithm jjwt
  • Part 5 : The jwt secure encrypted

A simple example

Dependent on the introduction of MAVN

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.10.7</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.10.7</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.10.7</version>
    <scope>runtime</scope>
</dependency>

one example

    
    // 生成密钥
    String key = "0123456789_0123456789_0123456789";
    SecretKey secretKey = new SecretKeySpec(key.getBytes(), SignatureAlgorithm.HS256.getJcaName());

    // 1. 生成 token
    String token = Jwts.builder()     // 创建 JWT 对象
            .setSubject("JSON Web Token")   // 设置主题(声明信息)
            .signWith(secretKey)    // 设置安全密钥(生成签名所需的密钥和算法)
            .compact(); // 生成token(1.编码 Header 和 Payload 2.生成签名 3.拼接字符串)
    System.out.println(token);

    //token = token + "s";

    // 2. 验证token,如果验证token失败则会抛出异常
    try {
        Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token);
        // OK, we can trust this token
        System.out.println("验证成功");
    } catch (JwtException e) {
        //don't trust the token!
        System.out.println("验证失败");
    }

    // 3. 解析token
    Claims body = Jwts.parser()     // 创建解析对象
            .setSigningKey(secretKey)   // 设置安全密钥(生成签名所需的密钥和算法)
            .parseClaimsJws(token)  // 解析token
            .getBody(); // 获取 payload 部分内容
    System.out.println(body);

Output:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKU09OIFdlYiBUb2tlbiJ9.QwmY_0qXW4BhAHcDpxz62v3xqkFYbg5lsZQhM2t-kVs
验证成功
{sub=JSON Web Token}

Common method

The following recommendations refer to the source code to know more details

Jwts.builder () creates DefaultJwtBuilder the object, the object common methods are as follows:

In compact()automatically header information setting method according to the signature algorithm, of course, also be set manually

  • setHeader(Header header): JwtBuilder
  • setHeader(Map<String, Object> header): JwtBuilder
  • setHeaderParams(Map<String, Object> params): JwtBuilder
  • setHeaderParam(String name, Object value): JwtBuilder

    Parameters Header objects by Jwts.header (); create, it is as simple as a map (you can use it as a map)

Payload

At least one claims, or when generating signatures will throw an exception

  • setClaims(Claims claims): JwtBuilder
  • setClaims(Map<String, Object> claims): JwtBuilder
  • addClaims(Map<String, Object> claims): JwtBuilder
  • setIssuer(String iss): JwtBuilder
  • setSubject(String sub): JwtBuilder
  • setAudience(String aud): JwtBuilder
  • setExpiration(Date exp): JwtBuilder
  • setNotBefore (Date nbf): JwtBuilder
  • setIssuedAt (Date IAT): JwtBuilder
  • setId (String JTI): JwtBuilder
  • claim(String name, Object value: JwtBuilder

Claims objects with similar parameters Header, created by Jwts.claims (), the same as simple as a map

It is noteworthy point is: Do not call setClaims (Claims claims) or setClaims (Map <String, Object> claims) after setXxx, because these two methods will cover all claim that has been set

Signature

  • signWith(Key key)
  • signWith(Key key, SignatureAlgorithm alg)
  • signWith(SignatureAlgorithm alg, byte[] secretKeyBytes)
  • signWith(SignatureAlgorithm alg, String base64EncodedSecretKey)
  • signWith(SignatureAlgorithm alg, Key key)

Key and algorithm, represent the key and the algorithm: The above methods provided the final two objects is
the same method used to generate the key internal methods and demo

SecretKey key = new SecretKeySpec(secretKeyBytes, alg.getJcaName());

Note: the length of the key must comply with the signature algorithm (avoid weak key)
HS256: 'bit length should be> = 256, i.e., the byte length of> = 32
HS384:' bit length should be> = 384, i.e., the byte length of> = 48
HS512: bit length should be> = 512, i.e., the byte length of> = 64
numbers in the secret key algorithms name represents the minimum bit length

More details signature algorithm, please refer to the signature algorithm section

Packaging Tools JWT

package com.liuchuanv.jwt;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;

import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

/**
 * JSON Web Token 工具类
 *
 * @author LiuChuanWei
 * @date 2019-12-11
 */
public class JwtUtils {

    /**
     * key(按照签名算法的字节长度设置key)
     */
    private final static String SECRET_KEY = "0123456789_0123456789_0123456789";
    /**
     * 过期时间(毫秒单位)
     */
    private final static long TOKEN_EXPIRE_MILLIS = 1000 * 60 * 60;

    /**
     * 创建token
     * @param claimMap
     * @return
     */
    public static String createToken(Map<String, Object> claimMap) {
        long currentTimeMillis = System.currentTimeMillis();
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTimeMillis))    // 设置签发时间
                .setExpiration(new Date(currentTimeMillis + TOKEN_EXPIRE_MILLIS))   // 设置过期时间
                .addClaims(claimMap)
                .signWith(generateKey())
                .compact();
    }

    /**
     * 验证token
     * @param token
     * @return 0 验证成功,1、2、3、4、5 验证失败
     */
    public static int verifyToken(String token) {
        try {
            Jwts.parser().setSigningKey(generateKey()).parseClaimsJws(token);
            return 0;
        } catch (ExpiredJwtException e) {
            e.printStackTrace();
            return 1;
        } catch (UnsupportedJwtException e) {
            e.printStackTrace();
            return 2;
        } catch (MalformedJwtException e) {
            e.printStackTrace();
            return 3;
        } catch (SignatureException e) {
            e.printStackTrace();
            return 4;
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            return 5;
        }
    }

    /**
     * 解析token
     * @param token
     * @return
     */
    public static Map<String, Object> parseToken(String token) {
        return Jwts.parser()  // 得到DefaultJwtParser
                .setSigningKey(generateKey()) // 设置签名密钥
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 生成安全密钥
     * @return
     */
    public static Key generateKey() {
       return new SecretKeySpec(SECRET_KEY.getBytes(), SignatureAlgorithm.HS256.getJcaName());
    }
}

Test code is as follows:

  //Map<String, Object> map = new HashMap<String, Object>();
        //map.put("userId", 1002);
        //map.put("userName", "张晓明");
        //map.put("age", 12);
        //map.put("address", "山东省青岛市李沧区");
        //String token = JwtUtils.createToken(map);
        //System.out.println(token);

        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0ZWM2NWNhNC0wZjVmLTRlOTktOTI5NS1mYWUyN2UwODIzYzQiLCJpYXQiOjE1NzY0OTI4NjYsImV4cCI6MTU3NjQ5NjQ2NiwiYWRkcmVzcyI6IuWxseS4nOecgemdkuWym-W4guadjuayp-WMuiIsInVzZXJOYW1lIjoi5byg5pmT5piOIiwidXNlcklkIjoxMDAyLCJhZ2UiOjEyfQ.6Z18aIA6y52ntQkV3BwlYiVK3hL3R2WFujjTmuvimww";
        int result = JwtUtils.verifyToken(token);
        System.out.println(result);

        Map<String, Object> map = JwtUtils.parseToken(token);
        System.out.println(map);

Output:

0
{jti=4ec65ca4-0f5f-4e99-9295-fae27e0823c4, iat=1576492866, exp=1576496466, address=山东省青岛市李沧区, userName=张晓明, userId=1002, age=12}

Signature Algorithm

12 kinds Signature Algorithm

JWT specification defines 12 standard signature algorithm: three kinds of secret key algorithms and asymmetric key algorithms 9 kinds of

  • HS256: HMAC using SHA-256
  • HS384: HMAC using SHA-384
  • HS512: HMAC using SHA-512
  • ES256: ECDSA using P-256 and SHA-256
  • ES384: ECDSA using P-384 and SHA-384
  • ES512: ECDSA using P-521 and SHA-512
  • RS256: RSASSA-PKCS-v1_5 using SHA-256
  • RS384: RSASSA-PKCS-v1_5 using SHA-384
  • RS512: RSASSA-PKCS-v1_5 using SHA-512
  • PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
  • PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
  • PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512

The name of the algorithm can be divided into four categories: HSxxx (secret key algorithms), ESxxx, RSxxx, PSxxx

HSxxx, ESxxx xxx represents the minimum key algorithm Bit length
RSxxx, PSxxx xxx represents the minimum key algorithm Byte length

The minimum length of the predetermined key is a short key in order to avoid weak key

Key generation

jjwt generated secret key two methods

String key = "1234567890_1234567890_1234567890";
// 1. 根据key生成密钥(会根据字节参数长度自动选择相应的 HMAC 算法)
SecretKey secretKey1 = Keys.hmacShaKeyFor(key.getBytes());
// 2. 根据随机数生成密钥
SecretKey secretKey2 = Keys.secretKeyFor(SignatureAlgorithm.HS256);
  • Method Keys.hmacShaKeyFor (byte []) is inside new SecretKeySpec (bytes, alg.getJcaName ()) to generate a key of
  • Method Keys.secretKeyFor (SignatureAlgorithm) Internal KeyGenerator.generateKey () to generate the key

jjwt also provides an asymmetric key pair generation method

// 1. 使用jjwt提供的方法生成
KeyPair keyPair = Keys.keyPairFor(SignatureAlgorithm.RS256);    //or RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512

// 2. 手动生成
int keySize = 1024;
// RSA算法要求有一个可信任的随机数源
SecureRandom secureRandom = new SecureRandom();
// 为RSA算法创建一个KeyPairGenerator对象 
KeyPairGenerator keyPairGenerator = null;
try {
    keyPairGenerator = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
}
// 利用上面的随机数据源初始化这个KeyPairGenerator对象
keyPairGenerator.initialize(keySize, secureRandom);
// 生成密钥对
KeyPair keyPair2 = keyPairGenerator.generateKeyPair();
  • Keys.keyPairFor (SignatureAlgorithm) will automatically generate the appropriate length according to the algorithm
  • signWith (secretKey) automatically select the appropriate algorithm according to the key length, can also specify any algorithm (algorithm specified key length is not limited, and can be arbitrarily selected, i.e., with a key generation RS256 can signWith (secretKey, SignatureAlgorithm.RS512 ), but JJWT this is not recommended)
  • KeyPair.getPrivate used when encrypting (), using keyPair.getPublic () decryption

Different key generation token

These are all using the same key signature generation of token, let's use a different key

This is a feature can be applied to different users / roles using different key generation token, help you build a better system privileges

  1. Header keyId in a first set (or Claims) in

  2. Define a class that inherits SigningKeyResolverAdapter, and rewrite resolveSigningKey () or resolveSigningKeyBytes () method

    public class MySigningKeyResolver extends SigningKeyResolverAdapter {
        @Override
        public Key resolveSigningKey(JwsHeader header, Claims claims) {
            // 除了从 header 中获取 keyId 外,也可以从 claims 中获取(前提是在 claims 中设置了 keyId 声明)
            String keyId = header.getKeyId();
            // 根据 keyId 查找相应的 key
            Key key = lookupVerificationKey(keyId);
            return key;
        }
    
        public Key lookupVerificationKey(String keyId) {
            // TODO 根据 keyId 获取 key,比如从数据库中获取
            // 下面语句仅做演示用,绝对不可用于实际开发中!!!
            String key = "qwertyuiopasdfghjklzxcvbnm2019_" + keyId;
            return Keys.hmacShaKeyFor(key.getBytes());
        }
    }
  3. When parsing, instead of calling setSigningKey (SecretKey), but calls setSigningKeyResolver (SigningKeyResolver)

    // 生成密钥
            // TODO 此处 keyId 仅做演示用,实际开发中可以使用 UserId、RoleId 等作为 keyId
            String keyId = new Long(System.currentTimeMillis()).toString();
            System.out.println("keyId=" + keyId);
    
            String key = "qwertyuiopasdfghjklzxcvbnm2019_" + keyId;
            SecretKey secretKey = new SecretKeySpec(key.getBytes(), SignatureAlgorithm.HS256.getJcaName());
    
            // 1. 生成 token
            String token = Jwts.builder()
                    .setHeaderParam(JwsHeader.KEY_ID, keyId)    // 设置 keyId(当然也可以在 claims 中设置)
                    .setSubject("JSON Web Token")
                    .signWith(secretKey)
                    .compact();
            System.out.println("token=" + token);
    
            // 2. 验证token
            // token 使用了不同的密钥生成签名,在解析时就不用调用 setSigningKey(SecretKey) 了
            // 而是调用 setSigningKeyResolver(SigningKeyResolver)
            try {
                Jwts.parser()
                        .setSigningKeyResolver(new MySigningKeyResolver())
                        .parseClaimsJws(token);
                // OK, we can trust this token
                System.out.println("token验证成功");
            } catch (JwtException e) {
                //don't trust the token!
                System.out.println("token验证失败");
            }
    

Security Encryption

Stay tuned .....

Guess you like

Origin www.cnblogs.com/lhat/p/12056759.html
jwt