Jwt令牌建立
阿新 • • 發佈:2020-12-09
新增依賴
<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中攜帶的資訊。