由于Http协议是无状态的,早期的前后端交互依赖的是Cookie+Session。服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。但是如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。
JWT的英文全称为Json Web Token,中文翻译为令牌。JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为 JSON 对象。此信息可以进行验证和信任,因为它是经过数字签名的。(来自官网的翻译)
JWT由3部分组成:头部(Header)、有效载荷(Payload)和验证签名(Signature)。
最后通过base64算法生成一串字符串。
JWT头是一个描述JWT元数据的JSON对象。
alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256)。
typ属性表示令牌的类型,JWT令牌统一写为JWT。
{
"alg": "HS256",
"typ": "JWT"
}
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。
载荷内部可以分为:
公共声明名称 | 公共声明描述 |
---|---|
iss | 发行者 |
sub | 主题 |
aud | 用户 |
exp | 过期时间 |
nbf | 在这个时间之前不可用 |
iat | 发布时间 |
jti | JWT的ID |
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
由于官网提供多种JWT实现(官方网站),这里只用到(com.auth0 / java-jwt)这个版本的jwt。
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.19.2version>
dependency>
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author Andy
* @Description 对Token常用方法进行封装
*/
public class TokenService {
/**
* 头部声明Map
*/
private Map<String, Object> headerClaim = new HashMap<>();
/**
* 载荷声明Map
*/
private Map<String, Object> payloadClaim = new HashMap<>();
/**
* 秒,用于设置过期时间单位
*/
protected static final long MILLIS_SECOND = 1000L;
/**
* 分钟,用于设置过期时间单位
*/
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
/**
* 密钥
*/
private final static String secret = "Andy's JWT DEMO";
private static TokenService service;
// 私有化构造器-单例模式
private TokenService() {
}
/**
* 懒汉式
*/
public static synchronized TokenService creator() {
if (service == null) {
service = new TokenService();
}
return service;
}
/**
* 对外头部声明Map修改接口
*
* @param headerClaim 头部声明Map
* @return 本对象实例
*/
public TokenService setHeaderClaim(Map<String, Object> headerClaim) {
this.headerClaim = headerClaim;
return this;
}
/**
* 对外载荷声明Map修改接口
*
* @param payloadClaim 载荷声明Map
* @return 本对象实例
*/
public TokenService setPayloadClaim(Map<String, Object> payloadClaim) {
this.payloadClaim = payloadClaim;
return this;
}
/**
* 创建Token对象方法
*/
public String createToken() {
return JWT.create()
// 设置过期时间为10分钟(这里可以自定义)
.withExpiresAt(new Date(System.currentTimeMillis() + 10 * MILLIS_MINUTE))
// 添加头部声明
.withHeader(headerClaim)
// 添加载荷声明
.withPayload(payloadClaim)
// 签发JWT字符串,这里需要传入一个加密算法
.sign(Algorithm.HMAC256(secret));
}
/**
* 校验Token是否有效
*
* @param tokenKey Token字符串
* @return true-false
*/
public static boolean checkToken(String tokenKey) {
try {
// 如果Token解析错误会抛出异常
getDecodeToken(tokenKey);
} catch (JWTVerificationException e) {
return false;
}
return true;
}
/**
* 对Token进行解析
*
* @param tokenKey Token字符串
* @return 解密后的Token对象
*/
private static DecodedJWT getDecodeToken(String tokenKey) {
return JWT.require(Algorithm.HMAC256(secret)).build().verify(tokenKey);
}
/**
* 获取一个指定名称的头部声明
*
* @param tokenKey Token字符串
* @param claimName 声明的Key值
* @return 声明对象
*/
public static Claim getHeaderClaim(String tokenKey, String claimName) {
return getDecodeToken(tokenKey).getHeaderClaim(claimName);
}
/**
* 获取一个指定名称的载荷声明
*
* @param tokenKey Token字符串
* @param claimName 声明的Key值
* @return 声明对象
*/
public static Claim getPayloadClaim(String tokenKey, String claimName) {
return getDecodeToken(tokenKey).getClaim(claimName);
}
/**
* 获取所有的载荷声明
*
* @param tokenKey Token字符串
* @return 声明对象的Map
*/
public static Map<String, Claim> getAllPayloadClaim(String tokenKey) {
return getDecodeToken(tokenKey).getClaims();
}
}
以上代码使用了单例设计模式(懒汉式),是我本人对Token操作的一些简易封装。JWT这个框架在公司项目中几乎都会用到,这里给刚入门的同学一些代码参考。
1.博客中标注原创的文章,版权归原作者 newCodeWorker(英文名Andy) 所有;
2.未经原作者允许不得转载本文内容,否则将视为侵权;
3.转载或者引用本文内容请注明来源及原作者;
4.对于不遵守此声明或者其他违法使用本文内容者,本人依法保留追究权等。