前言
相信大家都知道 jwt(json web token)协议(如果不太了解可以看 10分钟了解JSON Web令牌(JWT))是目前业界常用于跨域身份验证的约定方案,但是由于 json 解析性能太低,可能产生许多性能无法优化的问题。
· 为了能避免 json 解析所带来的性能损耗,我们可以使用自定义的分隔符区分不同元素,同时固定元素排序,达到原有 json 元素的能力。
· 同时缺省 header 部分,因为 header 部分通常用于声明 token 格式,但如果颁发 token 和校验 token 都是自己系统,完全可以缺省掉。
Encoder
Encoder 部分主要实现根据设置的 sub、exp、bnf 等元素生成 token 如下列简要实现:
public class SimpleJwtEncoder {
/**
* 生成 jwt token
* 实现 sub、exp、bnf 三种元素的传入,如需增加可以继续增加
* @param content
* @return
*/
public String createToken(String content, String exp, String nbf) throws Exception {
// 逗号区分 3 个元素,第一项是 sub,第二项是 exp,第三项是 nbf,无论是否有值,都会有对应的分隔符
StringBuilder sb = new StringBuilder();
sb.append(content);
sb.append(SimpleJwt.PAYLOAD_SEPARATOR);
if (exp != null) {
sb.append(exp);
}
sb.append(SimpleJwt.PAYLOAD_SEPARATOR);
if (nbf != null) {
sb.append(nbf);
}
return Base64.getUrlEncoder().encodeToString(sb.toString().getBytes())
+ "."
+ SignGenerator.genJwtSign(sb.toString());
}
/**
* 生成 jwt token
* 省略 bnf,允许不传 bnf 来构建 token
* @param content
* @return
*/
public String createToken(String content, String exp) throws Exception {
return createToken(content, exp, "");
}
而签名实现使用标准规范 Hmac256 也行,使用 md5 都行:
public class SignGenerator {
public static String genJwtSign(String content) {
String originSign = content + CommonConstants.JWT_TOKEN_SECRET;
return DigestUtils.md5DigestAsHex(originSign.getBytes());
}
}
Decoder
Decoder 需要对传入的 token 进行解析,主要阶段有 3 个:
· 转义 payload
· 校对签名
· 校对有效时间
针对上述三步操作的简要实现如下:
public class SimpleJwtDecoder {
private String payload;
private String sub;
private LocalDateTime exp;
private LocalDateTime nbf;
public SimpleJwtDecoder(String token) throws Exception {
String[] tokenList = StringUtils.split(token, ".");
String bContent = null;
String signToken = null;
if (tokenList.length == 2) {
bContent = tokenList[0];
signToken = tokenList[1];
} else {
throw new Exception(ErrorCode.ERROR_TOKEN_DECRPTY, "token 解码错误");
}
byte[] contentByte = Base64.getUrlDecoder().decode(bContent);
if (contentByte == null) {
throw new Exception(ErrorCode.ERROR_TOKEN_DECRPTY, "content 解码错误");
}
String payload = new String(contentByte);
// 验证签名是否正确
validateSign(payload, signToken);
String[] payloadArray = StringUtils.split(payload, SimpleJwt.PAYLOAD_SEPARATOR);
// 如果只有两个元素就是 sub 和 exp,再加一个元素的话就是 nbf
if (payloadArray.length == 2) {
setSub(payloadArray[0]);
setExp(LocalDateTime.ofEpochSecond(Integer.valueOf(payloadArray[1]),0, ZoneOffset.ofHours(8)));
} else if (payloadArray.length == 3) {
setSub(payloadArray[0]);
setExp(LocalDateTime.ofEpochSecond(Integer.valueOf(payloadArray[1]),0, ZoneOffset.ofHours(8)));
setNbf(LocalDateTime.ofEpochSecond(Integer.valueOf(payloadArray[2]),0, ZoneOffset.ofHours(8)));
} else {
throw new Exception(ErrorCode.ERROR_TOKEN_DECRPTY, "content 解码错误");
}
this.payload = payload;
}
public void verify() throws Exception {
long now = System.currentTimeMillis();
// 已超过限定时间
if (getExp() != null && now > getExp().toInstant(ZoneOffset.of("+8")).toEpochMilli()) {
throw new Exception(ErrorCode.ERROR_TOKEN_DECRPTY, "token 已过期");
}
// 还没到开始时间
if (getNbf() != null && now < getNbf().toInstant(ZoneOffset.of("+8")).toEpochMilli()) {
throw new Exception(ErrorCode.ERROR_TOKEN_DECRPTY, "token 还没开始");
}
}
private void validateSign(String payload, String signToken) throws Exception {
String sign = SignGenerator.genJwtSign(payload);
if (!sign.equals(signToken)) {
throw new Exception(ErrorCode.ERROR_TOKEN_DECRPTY, "验签失败");
}
}
public String getPayload() {
return payload;
}
public void setPayload(String payload) {
this.payload = payload;
}
public String getSub() {
return sub;
}
public void setSub(String sub) {
this.sub = sub;
}
public LocalDateTime getExp() {
return exp;
}
public void setExp(LocalDateTime exp) {
this.exp = exp;
}
public LocalDateTime getNbf() {
return nbf;
}
public void setNbf(LocalDateTime nbf) {
this.nbf = nbf;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。