1. 程式人生 > 實用技巧 >Jwt令牌建立

Jwt令牌建立

新增依賴

<dependencies>
        <!--   jwt     -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.51</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <resources>
            <!--注意這裡要加上這些,不然證書無法載入-->
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <excludes>
                    <exclude>**/*.jks</exclude>
                </excludes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*.jks</include>
                </includes>
            </resource>
        </resources>

    </build>

普通方式建立jwt令牌

package com.example.demo;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

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

/**
 * @author liuyalong
 */
public class JwtTest {

    public static void main(String[] args) {
        final String keyStr = "4379";
        Key key = new SecretKeySpec(keyStr.getBytes(), SignatureAlgorithm.HS512.getJcaName());

        // expire_time為token有效時長, 單位毫秒
        final long expire_time =10000;
        Date expiresDate = new Date(System.currentTimeMillis() + expire_time);

        //生成jwt令牌
        JwtBuilder jwtBuilder = Jwts.builder()
                //設定jwt編碼
                .setId("66")
                //設定jwt主題
                .setSubject("我是主題")
                //設定jwt簽發日期
                .setIssuedAt(new Date())
                .claim("a", "admin")
                .claim("b", "aaaa")
                .claim("c", "yalong")
                //設定jwt的過期時間,好像必須在claim後面
                .setExpiration(expiresDate)
                //注意,這裡的密碼應該使用Key型別,不應使用String型別
                //如果使用String型別,比如4379123加密,那麼解密使用4379也可以解開,
                // 因為這個String型別是base64EncodedSecretKey
                .signWith(SignatureAlgorithm.HS256, key);

        //生成jwt
        String jwtToken = jwtBuilder.compact();
        System.out.println(jwtToken);


        //解析jwt,得到其內部的資料
        Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(jwtToken).getBody();
        System.out.println(claims.get("exp"));
        System.out.println(claims);
    }
}

使用keytool生成證書

keytool -genkeypair -alias test -keyalg RSA -keypass yalong -keystore "D://yalong.jks" -storepass yalong -validity 3650 -dname "CN=localhost,OU=localhost,O=localhost,L=SH,ST=SH,C=CN" -storetype pkcs12

注意:這裡有個大坑,keypass 必須和storepass一樣,不然私鑰解不開,或許是keytool的原因造成的

  • alias:金鑰的別名
  • keyalg:使用的hash演算法
  • keypass:金鑰的訪問密碼
  • keystore:金鑰庫檔名,yalong.jks儲存了生成的證書
  • storepass:金鑰庫的訪問密碼
  • validity 3650 : 有效期10年
  • dname :證書的相關資訊,
    • CN:名字與形式
    • OU:組織單位名稱
    • O:組織機構名稱
    • L:城市資訊
    • ST:省份資訊
    • C:國家資訊

檢視金鑰庫

keytool -list -keystore "D://yalong.jks" -storepass liuyalong

匯出公鑰證書檔案.cer

keytool -export -alias test -keystore "D://yalong.jks" -file "D://publicKey.cer" -storepass yalong

使用金鑰對生成JWT並加密解密

使用私鑰生成JWT令牌,使用公鑰解密,公鑰可以公開,私鑰留在授權服務裡,公鑰可以匯出,私鑰不可以匯出

package com.example.demo;

import java.io.FileInputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.interfaces.RSAPrivateKey;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import org.apache.commons.codec.binary.Base64;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;

/**
 * @author liuyalong
 */
public class GetRsaKey {

    public static void main(String[] args) throws Exception {
        //證書庫檔案路徑
        // 這裡使用ClassPathResource路徑,所以不帶D:/
        String storePath = "yalong.jks";

        //這個路徑用來獲取私鑰
        String storePath1 = "D:/yalong.jks";

        //公鑰證書檔案路徑
        //從keystore 匯出公鑰證書檔案.cer,命令如下
        //keytool -export -alias test -keystore "D://yalong.keystore" -file "D://publicKey.cer"
        String cerPath = "D:/publicKey.cer";

        //證書別名
        String alias = "test";
        //證書庫密碼
        String storePw = "yalong";
        //證書密碼
        String keyPw = "yalong";
        String publicKey = getPublicKey(cerPath);
        String privateKey = getPrivateKey(storePath1, alias, storePw, keyPw);

        System.out.println("從證書獲取的公鑰為:" + publicKey);
        System.out.println("從證書獲取的私鑰為:" + privateKey);


        Map<String, String> map = new HashMap<>();
        map.put("company", "honbow");
        map.put("address", "shenzhen");
        String jwt = createJwt(storePath, alias, storePw, keyPw, map);
        System.out.println("生成JWT為:" + jwt);

        String s = parseJwt(jwt, publicKey);
        System.out.println("校驗後:" + s);
    }

    /**
     * 從公鑰證書中獲取公鑰
     *
     * @param cerPath 公鑰證書的路徑
     */
    private static String getPublicKey(String cerPath) throws Exception {
        CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509");
        FileInputStream fis = new FileInputStream(cerPath);
        // X509Certificate cert = (X509Certificate) certificatefactory.generateCertificate(fis);
        Certificate cert = certificatefactory.generateCertificate(fis);
        PublicKey pk = cert.getPublicKey();
        String pkString = new Base64().encodeToString(pk.getEncoded());
        //這裡需要拼接開頭和結尾,不然無法解析出來結果,可能有其他驗證方法
        return "-----BEGIN PUBLIC KEY-----" + pkString + "-----END PUBLIC KEY-----";
    }


    private static String getPublicKey(String cerPath, String instanceType) throws Exception {
        CertificateFactory certificatefactory = CertificateFactory.getInstance(instanceType);
        FileInputStream fis = new FileInputStream(cerPath);
        Certificate cert = certificatefactory.generateCertificate(fis);
        PublicKey pk = cert.getPublicKey();
        return new Base64().encodeToString(pk.getEncoded());
    }

    /**
     * @param storePath 證書庫的路徑
     * @param alias     證書別名,建立時指定的
     * @param storePw   訪問證書庫的密碼
     * @param keyPw     訪問證書的密碼
     * @param JwtMap    需要設定進Jwt的內容
     * @return jwt 字串
     */
    private static String createJwt(String storePath,
                                    String alias,
                                    String storePw,
                                    String keyPw,
                                    Map<String, String> JwtMap) {

        ClassPathResource classPathResource = new ClassPathResource(storePath);

        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource, keyPw.toCharArray());

        KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias, storePw.toCharArray());

        //將當前的私鑰轉換為rsa私鑰
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
        Jwt jwt = JwtHelper.encode(JSON.toJSONString(JwtMap), new RsaSigner(rsaPrivateKey));
        return jwt.getEncoded();
    }

    /**
     * 生成私鑰
     *
     * @param storePath 證書庫的路徑
     * @param alias     證書別名,建立時指定的
     * @param storePw   訪問證書庫的密碼
     * @param keyPw     訪問證書的密碼
     */
    private static String getPrivateKey(String storePath, String alias, String storePw, String keyPw) throws Exception {
        FileInputStream is = new FileInputStream(storePath);
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(is, storePw.toCharArray());
        is.close();
        PrivateKey key = (PrivateKey) ks.getKey(alias, keyPw.toCharArray());
        return new Base64().encodeToString(key.getEncoded());
    }

    private static String getPrivateKey(String storePath, String alias, String storePw, String keyPw, String storeType) throws Exception {
        FileInputStream is = new FileInputStream(storePath);
        KeyStore ks = KeyStore.getInstance(storeType);
        ks.load(is, storePw.toCharArray());
        is.close();
        PrivateKey key = (PrivateKey) ks.getKey(alias, keyPw.toCharArray());
        return new Base64().encodeToString(key.getEncoded());
    }


    /**
     * @param jwt jwt字串
     * @param publicKey 公鑰
     * @return 解密後的字串token
     */
    private static String parseJwt(String jwt, String publicKey) {

        Jwt token = JwtHelper.decodeAndVerify(jwt, new RsaVerifier(publicKey));

        return token.getClaims();
    }
}

RSA的公鑰和私鑰到底哪個才是用來加密和哪個用來解密?

  • 既然是加密,那肯定是不希望別人知道我的訊息,所以只有我才能解密,所以可得出公鑰負責加密,私鑰負責解密;同理,既然是簽名,那肯定是不希望有人冒充我發訊息,只有我才能釋出這個簽名,所以可得出私鑰負責簽名,公鑰負責驗證。

注:

JWT保證的是資料傳輸過程中的完整性而不是機密性。
由於payload是使用base64url編碼的,所以相當於明文傳輸,如果在payload中攜帶了敏感資訊(如存放金鑰對的檔案路徑),單獨對payload部分進行base64url解碼,就可以讀取到payload中攜帶的資訊。