序
温故而知新,可以为师矣。
从前面的博客JWT就是这么简单知道了JWT的生成规则是使用BASE64编码。
所以朕水今天就带大家来写一个简单的生成Token的小工具,让大家更能方便理解JWT。
token加密算法
朕水只示范SHA256的实现,需要更多扩展则需要小伙伴们的动手能力了嗷
写一个算法的接口,接口定义算法类型和签名
//算法接口
public interface Algorithm {
//算法类型
String alg();
//算法返回的签名
String algorithm(String message);
}
朕水写一个SHA256的算法实现
public class HMAC256Algorithm implements Algorithm {
//算法类型
private static final String ALG ="HS256";
//密钥
private final String secret;
public HMAC256Algorithm(String secret){
//构造时传入密钥
if(secret==null||"".equals(secret)){
throw new RuntimeException("must has secret");
}
this.secret = secret;
}
@Override
public String algorithm(String message) {
String hash = "";
try {
//使用对应的算法加密
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
//将 加密的 二进制结果进行Base64-URL编码
//Base64-URL编码规则: +变为- /变为_ 去掉等号=
hash = Base64.encodeBase64URLSafeString(mac.doFinal(message.getBytes()));
} catch (Exception e) {
e.printStackTrace();
}
return hash;
}
@Override
public String alg() {
//返回算法类型
return ALG;
}
}
在URL中字符+和/是不能在url中做为参数的,所以base64有一种urlSafe编码。在JWT官网上有对应的说明是使用BaseUrl进行编码。
token生成逻辑
public class JWSBuilder {
private Algorithm algorithm;
private Map<String,String> header = new HashMap<>();
private Map<String,Object> payloadMap = new HashMap<>();
private String payloadBody;
public JWSBuilder(Algorithm algorithm) {
this.algorithm = algorithm;
}
public JWSBuilder header(Map<String,String> header) {
this.header = header;
return this;
}
public JWSBuilder sub(String sub) {
payloadMap.put("sub",sub);
return this;
}
public JWSBuilder iat(Date iat) {
payloadMap.put("iat",iat.getTime());
return this;
}
public JWSBuilder claim(String key,String value){
payloadMap.put(key,value);
return this;
}
public JWSBuilder payloadBody(String payloadBody){
this.payloadBody = payloadBody;
return this;
}
public String build() {
if(payloadMap.isEmpty()&&(payloadBody==null||"".equals(payloadBody.trim()))){
throw new RuntimeException("not has payload message");
}
//将载荷拼接为JSON
header.put("alg",algorithm.alg());
StringBuilder headerSb = new StringBuilder("{");
header.forEach((key,val)->{
headerSb.append("\"");
headerSb.append(key);
headerSb.append("\":");
headerSb.append("\"");
headerSb.append(val);
headerSb.append("\"");
headerSb.append(",");
});
headerSb.replace(headerSb.length()-1,headerSb.length(),"}");
//如果为空或者有载荷实体payloadBody则不拼接JSON
if(payloadBody==null){
StringBuilder payloadSb = new StringBuilder("{");
payloadMap.forEach((key,val)->{
payloadSb.append("\"");
payloadSb.append(key);
payloadSb.append("\":");
payloadSb.append("\"");
payloadSb.append(val);
payloadSb.append("\"");
payloadSb.append(",");
});
payloadSb.replace(payloadSb.length()-1,payloadSb.length(),"}");
payloadBody=payloadSb.toString();
}
String header = Base64.encodeBase64URLSafeString(headerSb.toString().getBytes());
String payload = Base64.encodeBase64URLSafeString(payloadBody.getBytes());
String sign = this.algorithm.algorithm(header + "." + payload);
return header+"."+payload+"."+sign;
}
}
上面的代码朕水只写了sub、iat的简单封装,需要扩展的小伙伴需要自己写对应的代码。生成token的Builder类就写好了,朕水去测试一下。
测试生成token
Algorithm hmac256Algorithm = new HMAC256Algorithm("zhenshui");
String token = new JWSBuilder(hmac256Algorithm)
.sub("demo")
.claim("auth","zhenshui")
.build();
System.out.println(token);
token生成成功!
将token拿去官网debugger一下试试是否正确。朕水测试是正确的。
校验token
接下来朕水写一个校验类,对token进行校验。和上面一样,朕水只写一个简单的校验,需要更复杂的逻辑则需要小伙伴自己手动diy了
public class JWSParser {
private Algorithm algorithm;
private String token;
private String header;
private String payloadBody;
private Map<String,String> verifyMap;
public JWSParser(Algorithm algorithm) {
this.algorithm = algorithm;
}
public JWSParser sub(String sub){
verifyMap.put("sub",sub);
return this;
}
public String getHeader() {
return header;
}
public String getPayloadBody() {
return payloadBody;
}
public JWSParser parser(String token){
//截取前面两节
String perHeaderAndPayloadToken = token.substring(0, token.lastIndexOf("."));
//重新加密
String algorithm = this.algorithm.algorithm(perHeaderAndPayloadToken);
//截取后面一节进行比较
String signStr = token.substring(token.lastIndexOf(".")+1);
if (!signStr.equals(algorithm)) {
//签名不一致
throw new RuntimeException("token exception");
}
//得到载体、转化
String payload = perHeaderAndPayloadToken.substring( perHeaderAndPayloadToken.indexOf(".")+1);
payloadBody = new String(Base64.decodeBase64URLSafe(payload));
//校验字段:过期时间、主体、签发人等等信息
//TODO
//得到头部、转化
String headerBase64Str = perHeaderAndPayloadToken.substring(0, perHeaderAndPayloadToken.indexOf("."));
this.header = new String(Base64.decodeBase64URLSafe(headerBase64Str));
return this;
}
}
上面的校验类写完了,但是校验并没有写完整,只是简单的做了签名的校验。如果不依赖第三方库转化JSON比较复杂,所以朕水就没有写。
校验token测试
写完了之后,朕水去校验一下token
解析成功,好啦。相信小伙伴们看完这篇后对常用的JWS的生成和解密已经很熟悉了。
tips: 如果是非对称加密则需要对应的加密和解密方法。
目前的JWT框架都比较完善且有专人进行维护,小伙伴不要在实际项目中使用自己写的Token生成。
如果不知道使用哪一个JWT框架,可以看看朕水的文章JWT框架简单测评,哪款是你的菜