JWT Token 使用 RS256 和 ES256 簽名

使用 RS256 簽名

創建 RSA256 密鑰

安裝 openssl 服務,執行如下指令生成密鑰對。

# 創建私鑰
openssl genrsa -out rsa_private.pem 2048
# 創建公鑰
openssl rsa -in rsa_private.pem -outform PEM -pubout -out rsa_public.pem

創建和驗證 JWT Token

將創建好的密鑰對放到 resources/crt 目錄下,讀取私鑰進行簽名,讀取公鑰進行驗籤。可自定義 token 中攜帶的信息,確保自定義信息中沒有敏感信息,例如密碼、手機號等。

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.util.StringUtils;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;

public class JwtUtils {

    public static final String DEVICE_ID = "device_id";
    public static final long EXPIRE = 10 * 60 * 60;

    public static void main(String[] args) throws Exception {
        String token = createAccessJwtToken("YJ1001");
        System.err.println(token);
        boolean validatorToken = validatorToken(token);
        System.err.println(validatorToken);
        String id = parseAccessJwtToken(token);
        System.err.println(id);
    }

    /**
     * 生成token字符串的方法
     *
     * @param deviceId
     * @return
     */
    public static String createAccessJwtToken(String deviceId) {
        PrivateKey privateKey = getRSAPrivateKey();

        Claims claims = Jwts.claims().setSubject(deviceId);
        claims.put(DEVICE_ID, deviceId);

        String accessToken = Jwts.builder()
                .setClaims(claims)
                .setIssuer("User")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .signWith(SignatureAlgorithm.ES256, privateKey)
                .compact();

        return accessToken;
    }

    /**
     * 判斷token是否存在與有效
     *
     * @param accessToken
     * @return
     */
    public static boolean validatorToken(String accessToken) {
        try {
            if (StringUtils.isEmpty(accessToken)) {
                return false;
            }
            PublicKey publicKey = getRSAPublicKey();
            Jwts.parser().setSigningKey(publicKey).parseClaimsJws(accessToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根據token獲取deviceId
     *
     * @param accessToken
     * @return
     */
    public static String parseAccessJwtToken(String accessToken) {
        try {
            if (StringUtils.isEmpty(accessToken)) {
                return null;
            }
            PublicKey publicKey = getRSAPublicKey();
            Jws<Claims> jws = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(accessToken);
            Claims claims = jws.getBody();
            return claims.get(DEVICE_ID, String.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 獲取PrivateKey對象
     *
     * @return
     */
    private static PrivateKey getRSAPrivateKey() {
        try {
            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("crt/rsa_private.pem");
            String privateKeyBase64 = IOUtils.toString(inputStream, "UTF-8");
            String privateKeyPEM = privateKeyBase64.replaceAll("\\-*BEGIN.*KEY\\-*", "")
                    .replaceAll("\\-*END.*KEY\\-*", "")
                    .replaceAll("\r", "")
                    .replaceAll("\n", "");
            byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);
            DerInputStream derReader = new DerInputStream(encoded);
            DerValue[] seq = derReader.getSequence(0);
            if (seq.length < 9) {
                throw new GeneralSecurityException("Could not read private key");
            }
            // skip version seq[0];
            BigInteger modulus = seq[1].getBigInteger();
            BigInteger publicExp = seq[2].getBigInteger();
            BigInteger privateExp = seq[3].getBigInteger();
            BigInteger primeP = seq[4].getBigInteger();
            BigInteger primeQ = seq[5].getBigInteger();
            BigInteger expP = seq[6].getBigInteger();
            BigInteger expQ = seq[7].getBigInteger();
            BigInteger crtCoeff = seq[8].getBigInteger();
            RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, primeP, primeQ, expP, expQ, crtCoeff);
            KeyFactory factory = KeyFactory.getInstance("RSA");
            return factory.generatePrivate(keySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 獲取PublicKey對象
     *
     * @return
     */
    private static PublicKey getRSAPublicKey() {
        try {
            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("crt/rsa_public.pem");
            String publicKeyBase64 = IOUtils.toString(inputStream, "UTF-8");
            String publicKeyPEM = publicKeyBase64.replaceAll("\\-*BEGIN.*KEY\\-*", "")
                    .replaceAll("\\-*END.*KEY\\-*", "")
                    .replaceAll("\r", "")
                    .replaceAll("\n", "");
            Security.addProvider(new BouncyCastleProvider());
            X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyPEM));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
            return publicKey;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

使用 ES256 加密

創建 ES256 密鑰

安裝 openssl 服務,執行如下指令生成密鑰對,這裏注意需要將私鑰轉換爲 pkcs8 格式。

# 創建私鑰
openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem
# 創建公鑰
openssl ec -in ec_private.pem -pubout -out ec_public.pem
# 轉換私鑰
openssl pkcs8 -topk8 -inform PEM -outform DER -in ec_private.pem -nocrypt > ec_private_pkcs8

創建和驗證 JWT Token

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.util.StringUtils;

import java.io.InputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;

public class JwtUtils {

    public static final String DEVICE_ID = "device_id";
    public static final long EXPIRE = 10 * 60 * 60;

    public static void main(String[] args) {
        String token = createAccessJwtToken("YJ1001");
        System.err.println(token);
        boolean validatorToken = validatorToken(token);
        System.err.println(validatorToken);
        String id = parseAccessJwtToken(token);
        System.err.println(id);
    }

    /**
     * 生成token字符串的方法
     *
     * @param deviceId
     * @return
     */
    public static String createAccessJwtToken(String deviceId) {
        PrivateKey privateKey = getECPrivateKey();

        Claims claims = Jwts.claims().setSubject(deviceId);
        claims.put(DEVICE_ID, deviceId);

        String accessToken = Jwts.builder()
                .setClaims(claims)
                .setIssuer("User")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .signWith(SignatureAlgorithm.ES256, privateKey)
                .compact();

        return accessToken;
    }

    /**
     * 判斷token是否存在與有效
     *
     * @param accessToken
     * @return
     */
    public static boolean validatorToken(String accessToken) {
        try {
            if (StringUtils.isEmpty(accessToken)) {
                return false;
            }
            PublicKey publicKey = getECPublicKey();
            Jwts.parser().setSigningKey(publicKey).parseClaimsJws(accessToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根據token獲取deviceId
     *
     * @param accessToken
     * @return
     */
    public static String parseAccessJwtToken(String accessToken) {
        try {
            if (StringUtils.isEmpty(accessToken)) {
                return null;
            }
            PublicKey publicKey = getECPublicKey();
            Jws<Claims> jws = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(accessToken);
            Claims claims = jws.getBody();
            return claims.get(DEVICE_ID, String.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 獲取PrivateKey對象
     *
     * @return
     */
    private static PrivateKey getECPrivateKey() {
        try {
            Security.addProvider(new BouncyCastleProvider());
            KeyFactory keyFactory = KeyFactory.getInstance("ECDH", "BC");
            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("crt/ec_private_pkcs8");
            byte[] devicePriKeybytes = IOUtils.toByteArray(inputStream);
            PKCS8EncodedKeySpec devicePriKeySpec = new PKCS8EncodedKeySpec(devicePriKeybytes);
            return keyFactory.generatePrivate(devicePriKeySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 獲取PublicKey對象
     *
     * @return
     */
    private static PublicKey getECPublicKey() {
        try {
            Security.addProvider(new BouncyCastleProvider());
            KeyFactory keyFactory = KeyFactory.getInstance("ECDH", "BC");

            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("crt/ec_public.pem");
            String publicKeyBase64 = IOUtils.toString(inputStream, "UTF-8");

            publicKeyBase64 = publicKeyBase64.replaceAll("\\-*BEGIN.*KEY\\-*", "")
                    .replaceAll("\\-*END.*KEY\\-*", "")
                    .replaceAll("\r", "")
                    .replaceAll("\n", "");
            byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64);

            X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(publicKeyBytes);
            return keyFactory.generatePublic(pubX509);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章