1. 前言
Json Web Token
(
JWT
) 近几年是前后端分离常用的
Token
技术,是目前最流行的跨域身份验证解决方案。你可以通过文章
一文了解web无状态会话token技术JWT
来了解
JWT
。今天我们来手写一个通用的
JWT
服务。
DEMO
获取方式在文末,实现在
jwt
相关包下
2. spring-security-jwt
spring-security-jwt
是
Spring Security Crypto
提供的
JWT
工具包 。
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>${spring-security-jwt.version}</version>
</dependency> 复制代码
核心类只有一个:
org.springframework.security.jwt.JwtHelper
。它提供了两个非常有用的静态方法。
3. JWT 编码
JwtHelper
提供的第一个静态方法就是
encode(CharSequence content, Signer signer)
这个是用来生成jwt的方法 需要指定
payload
跟
signer
签名算法。
payload
存放了一些可用的
不敏感
信息:
-
iss
jwt签发者 -
sub
jwt所面向的用户 -
aud
接收jwt的一方 -
iat
jwt的签发时间 -
exp
jwt的过期时间,这个过期时间必须要大于签发时间iat
-
jti
jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
除了以上提供的基本信息外,我们可以定义一些我们需要传递的信息,比如目标用户的权限集 等等。
切记不要传递密码等敏感信息
,因为
JWT
的前两段都是用了
BASE64
编码,几乎算是明文了。
3.1 构建 JWT 中的 payload
我们先来构建 payload :
/**
* 构建 jwt payload
*
* @author Felordcn
* @since 11:27 2019/10/25
**/
public class JwtPayloadBuilder {
private Map<String, String> payload = new HashMap<>();
/**
* 附加的属性
*/
private Map<String, String> additional;
/**
* jwt签发者
**/
private String iss;
/**
* jwt所面向的用户
**/
private String sub;
/**
* 接收jwt的一方
**/
private String aud;
/**
* jwt的过期时间,这个过期时间必须要大于签发时间
**/
private LocalDateTime exp;
/**
* jwt的签发时间
**/
private LocalDateTime iat = LocalDateTime.now();
/**
* 权限集
*/
private Set<String> roles = new HashSet<>();
/**
* jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
**/
private String jti = IdUtil.simpleUUID();
public JwtPayloadBuilder iss(String iss) {
this.iss = iss;
return this;
}
public JwtPayloadBuilder sub(String sub) {
this.sub = sub;
return this;
}
public JwtPayloadBuilder aud(String aud) {
this.aud = aud;
return this;
}
public JwtPayloadBuilder roles(Set<String> roles) {
this.roles = roles;
return this;
}
public JwtPayloadBuilder expDays(int days) {
Assert.isTrue(days > 0, "jwt expireDate must after now");
this.exp = this.iat.plusDays(days);
return this;
}
public JwtPayloadBuilder additional(Map<String, String> additional) {
this.additional = additional;
return this;
}
public String builder() {
payload.put("iss", this.iss);
payload.put("sub", this.sub);
payload.put("aud", this.aud);
payload.put("exp", this.exp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
payload.put("iat", this.iat.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
payload.put("jti", this.jti);
if (!CollectionUtils.isEmpty(additional)) {
payload.putAll(additional);
}
payload.put("roles", JSONUtil.toJsonStr(this.roles));
return JSONUtil.toJsonStr(JSONUtil.parse(payload));
}
}复制代码
通过建造类
JwtClaimsBuilder
我们可以很方便来构建
JWT
所需要的
payload
json
字符串传递给
encode(CharSequence content, Signer signer)
中的
content
。
3.2 生成 RSA 密钥并进行签名
为了生成
JWT Token
我们还需要使用
RSA
算法来进行签名。 这里我们使用
JDK
提供的证书管理工具
Keytool
来生成
RSA 证书
,格式为
jks
格式。
生成证书命令参考:
keytool -genkey -alias felordcn -keypass felordcn -keyalg RSA -storetype PKCS12 -keysize 1024 -validity 365 -keystore d:/keystores/felordcn.jks -storepass 123456 -dname "CN=(Felord), OU=(felordcn), O=(felordcn), L=(zz), ST=(hn), C=(cn)"
复制代码
其中
-alias felordcn -storepass 123456
我们要作为配置使用要记下来。我们要使用下面定义的这个类来读取证书
package cn.felord.spring.security.jwt;
import org.springframework.core.io.ClassPathResource;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.RSAPublicKeySpec;
/**
* KeyPairFactory
*
* @author Felordcn
* @since 13:41 2019/10/25
**/
class KeyPairFactory {
private KeyStore store;
private final Object lock = new Object();
/**
* 获取公私钥.
*
* @param keyPath jks 文件在 resources 下的classpath
* @param keyAlias keytool 生成的 -alias 值 felordcn
* @param keyPass keytool 生成的 -keypass 值 felordcn
* @return the key pair 公私钥对
*/
KeyPair create(String keyPath, String keyAlias, String keyPass) {
ClassPathResource resource = new ClassPathResource(keyPath);
char[] pem = keyPass.toCharArray();
try {
synchronized (lock) {
if (store == null) {
synchronized (lock) {
store = KeyStore.getInstance("jks");
store.load(resource.getInputStream(), pem);
}
}
}
RSAPrivateCrtKey key = (RSAPrivateCrtKey) store.getKey(keyAlias, pem);
RSAPublicKeySpec spec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent());
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(spec);
return new KeyPair(publicKey, key);
} catch (Exception e) {
throw new IllegalStateException("Cannot load keys from store: " + resource, e);
}
}
}复制代码