摘要:token验证,机制实现
一、cookie验证和token验证的比较
cookie验证的机制是通过在客户端生成cookie,在服务器端生成session,然后每次请求时通过核对前端传来的cookie和服务器端session是否一致来管理用户的状态。当我们关闭浏览器的时候session会被释放,而cookie也可以自定义失效时间使其在一定时间内失效。
token的验证机制放弃了session,为用户生成包含各类信息的一个集合,将其编码为一个令牌(token),将这个令牌发给前端,前端每次需要验证身份时携带令牌,服务器检查令牌的合法性以验证身份。简化了服务器的存储,但是要比session+cookie的验证方式更加消耗计算。是一种以时间换空间的方式。
相较于cookie验证的几点好处:
- 支持跨域:cookie是不支持跨域的,token则可以做到。这一点对于我们项目深有感触,前端和后台是分离开发和测试,前端的测试常常需要跨域测试,没有token之前总是统一汇总测试,很麻烦,也想当拖节奏。
- 无状态:因为cookie验证是依赖于session的状态的,而token则完全不用在session中保存任何数据,所以可以做到无状态,只要有token并且合法就可以。
二、token的组成和简单实现
token主要有三个部分组成:头部,载荷,签名。
头部和载荷使用的是base64编码的,签名则是使用的HS256。当时不是很明白这个HS256,实际上就是HMAC using SHA-256。一种带密钥的加密。base64可以利用java有自带的工具Base64Encoder。
具体参考下面的博客:
https://blog.csdn.net/weixin_39800144/article/details/78853323
我们通过一个类来实现这个token的功能
此处使用了导入了Jackson的jar包,方便将对象转化为json,不然需要手动的转化。
import java.util.Date;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.thoughtworks.xstream.core.util.Base64Encoder;
import com.zhiku.util.Data;
public class Token {
//JWT的header,实际工作中从配置文件中获得
public static String Header = "{\"typ\": \"JWT\",\"alg\": \"HS256\"}";
//密钥
public static String SECRET_KEY = "secret";
//base64编码工具
public static Base64Encoder be = new Base64Encoder();
public static String getToken(Data message) throws Exception{
String header = setHeader();
String payload = setPayload(message);
String signature = setSignature(header + "." + payload);
return header + "." + payload + "." + signature;
}
/**
* 设置JWT的荷载payload,荷载中包含主要的信息
* @param payload 一个可以格式化为json的对象
* @return
* @throws Exception
*/
public static String setPayload(Object payload) throws Exception{
String base64_payload = om.writeValueAsString(payload);
return be.encode(base64_payload.getBytes());
}
public static Object getPayload(String base64_payload) throws Exception{
byte[] h = be.decode(base64_payload);
return om.readValue(h, Object.class);
}
/**
* 设置header
* 获取已有的header,然后生成base64编码
* @return
*/
public static String setHeader(){
return be.encode(Header.getBytes());
}
/**
* 解析header
* @param base64_Header base64编码的header部分
* @return 返回解码后的header,以一个data的形式返回对应的json
* @throws Exception 在将json转变为data对象时可能出现异常
*/
public static Object getHeader(String base64_Header) throws Exception{
byte[] h = be.decode(base64_Header);
return om.readValue(h, Object.class);
}
/**
* 将header和payload使用HS256加密
* 然后对加密信息进行base64编码
* 生成JWT的签名
* @param message header.payload
* @return JWT的签名
* @throws Exception
*/
public static String setSignature(String message) throws Exception{
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(SECRET_KEY.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
String hash = be.encode(sha256_HMAC.doFinal(message.getBytes()));
return hash;
}
/**
* 验证token的正确性
* @param token 待验证token
* @return token是否正确
*/
public static boolean testSign(String token){
boolean equal = false;
String fore_message = token.substring(0,token.lastIndexOf('.'));
String sign = token.substring(token.lastIndexOf('.')+1);
try{
if(sign.equals(setSignature(fore_message))){
equal = true;
}else{
equal = false;
}
}catch(Exception e){
equal = false;
}
return equal;
}
}
注意:在经过base64转码之后有些时候会包含“+”,在前端传给后台的时候,“+”会变成“ ”(加号变成了空格)。这个时候就会影响token的验证,可以使用字符串将其替换。