引入
最近要开发一个谔码者管理平台,登录模块的技术选型我选择了JWT
Jwt可以保证放在客户端的登录信息不被篡改。
为了更加安全,加密算法我采用RSA非对称加密。即使用私钥生成Jwt,公钥校验Jwt。
问题
一开始想用spring的security框架,因为其中包含了JWT、Bcrypt以及其他内容,而且使用JwtHelper类,可以很方便的用私钥生成JWT,公钥解密。
但是搜遍全网,没找到JwtHelper设置Jwt过期时间的方法。
有个博主说,可以在载荷中手动增加iat、exp数据。但是经过测试,还是不行。
最终还是决定使用Jwts进行加密解密
JWT的加密解密
代码中提供Jwts和JwtHelper两种方式的加解密
第一步:生成密钥
通过keyTool(java提供的证书管理工具)生成
只要安装了JDK,就有这个工具。可以在cmd界面,输入keytool,检测是否有该工具。
若出现:
‘keytool’ 不是内部或外部命令,也不是可运行的程序或批处理文件。
可以进入jdk安装目录,bin文件夹下,就有keytool.exe文件。
此时,在地址栏输入cmd,回车,即可在此处打开cmd窗口。
执行以下命令:
// 以下指令为一行,设置多行是方便阅读
keytool -genkeypair
-alias 密钥别名
-keyalg 使用的算法
-keypass 密钥的访问密码
-keystore 生成的密钥库文件名,扩展名是jks
-storepass 密钥库的访问密码,用来打开jks文件
输入信息后,即可在当前目录生成jks文件。
第二步:导出公钥
对于微服务来说,私钥放在认证服务上,其他服务只需要存公钥即可。因为其他服务只做校验,不加密JWT
可以通过openssl导出密钥,即字符串格式的公钥或私钥
openssl需要安装,点击此处跳转下载
安装好之后,增加环境变量:
执行以下命令即可:
keytool -list -rfc --keystore jks文件(包含扩展名.jks) | openssl x509 -inform pem -pubkey
将 -----BEGIN PUBLIC KEY----- 与 -----END PUBLIC KEY----- 中的内容复制,新建文件并存放。
一般是创建public.key
将其放入项目的resource目录下,即可
第三步:加密JWT
/**
* 根据私钥生成JWT令牌
*
* @author Eugenema
* @date 2022/2/19 17:49
*
* @param userInfo 用户信息
*
* @return 加密后的JWT
**/
public String createJWT(UserInfo userInfo){
//载荷
Map<String,Object> payload = new HashMap<>(2);
payload.put("id", userInfo.getId());
payload.put("userName",userInfo.getName());
/** 私钥:resource目录下 */
ClassPathResource keyFileResource = new ClassPathResource("emPerson.jks");
//创建秘钥工厂,参数为:秘钥文件、秘钥库密码
//import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
//pom文件:
/**
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
<version>1.0.11.RELEASE</version>
</dependency>
*/
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(keyFileResource, "密钥库密码password".toCharArray());
//获取秘钥,参数为:别名,秘钥密码
KeyPair keyContent = keyStoreKeyFactory.getKeyPair("alias别名", "秘钥的密码".toCharArray());
/** 私钥 */
PrivateKey privateKey = keyContent.getPrivate();
//通过JwtHelper生成JWT
//由于无法设置过期时间,被弃用
// Jwt jwtContent = JwtHelper.encode(JSON.toJSONString(payload), new RsaSigner(privateKey));
// String jwtEncoded = jwtContent.getEncoded();
// System.out.println(jwtEncoded);
//通过Jwts生成JWT
//pom文件:
/**
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
*/
JwtBuilder eugeneMa = Jwts.builder()
//设置载荷
.addClaims(payload)
//设置ID,据说能防止重放攻击
.setId(String.valueOf(System.currentTimeMillis()))
//设置主题
.setSubject("eugeneMa")
//设置签发时间
.setIssuedAt(new Date())
//设置过期时间
.setExpiration(new Date(System.currentTimeMillis() + 30000))
//设置加密算法及私钥
.signWith(SignatureAlgorithm.RS256, privateKey);
//返回JWT
return eugeneMa.compact();
}
第四步:解密JWT
/**
* 解析JWT
*
* @author Eugenema
* @date 2022/2/19 18:47
*
* @param jwt 要解密的JWT
*
* @return 解析后的jwt
**/
public String parseJwt(String jwt){
//通过JwtHelper解析
//Jwt token = JwtHelper.decodeAndVerify(jwt, new RsaVerifier(getPubKey));
//return token.getClaims();
//通过Jwts解析
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getPubKey()).parseClaimsJws(jwt);
return claimJws.getBody();
}
/**
* 获取公钥
*
* @author Eugenema
* @date 2022/2/19 19:01
*
* @return 公钥,若获取失败则返回null
**/
private static PublicKey getPubKey() {
//指向resource目录下的公钥文件
Resource publicKey = new ClassPathResource("public.key");
try {
InputStreamReader publicKeyIs = new InputStreamReader(publicKey.getInputStream());
BufferedReader publicKeyBr = new BufferedReader(publicKeyIs);
StringBuilder publicKeySb = new StringBuilder();
String line;
//将文件中的多行变为一行
while ((line = publicKeyBr.readLine()) != null) {
publicKeySb.append(line);
}
//将String转换成java的PublicKey对象
byte[] byteKey = Base64.getDecoder().decode(publicKeySb.toString());
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(byteKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(x509EncodedKeySpec);
} catch (Exception e) {
logger.error("获取公钥异常!", e);
return null;
}
}