目录
系列文章目录
前言
一、JWT是什么?
二、什么时候你应该用JSON Web Tokens
三、JSON Web Token的结构
Header
四、使用步骤
1.引入库
2.单元测试
总结
前言
本文主要介绍JWT是什么,JWT的组成结构,以及如何创建JWT。
一、JWT是什么?
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
二、什么时候用JSON Web Tokens
- Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
三、JSON Web Token的结构
jwt 的token 格式 如: aaa.bbb.ccc
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VOYW1lIjoieWFuZ3lhbmdwaW5nIiwidXNlclR5cGUiOjEsImV4cCI6MTY3ODA3MjcwNSwidXNlcklkIjoxfQ.crrtA7O7vcSkMPEmqazIbQwf29u1_uqCrBvft6o2Wn8
JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:
- Header 请求头
- Payload 载荷
- Signature 签名
Header
header典型组成:算法名称(比如:HMAC SHA256或者RSA等等)。例如:
{
"alg": "HS256"
}
然后,用Base64对这个JSON编码就得到JWT的第一部分
eyJhbGciOiJIUzI1NiJ9
Base64 Header测试代码
public static void main(String[] args) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("alg", "HS256");
String json = JSON.toJSONString(map);
System.out.println(json);
byte[] textByte = json.getBytes("UTF-8");
System.out.println(Base64.getEncoder().encodeToString(textByte));
}
运行结果如下:
{"alg":"HS256"}
eyJhbGciOiJIUzI1NiJ9
JWT Header类定义如下:
public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header<T> {
public DefaultHeader() {
}
public DefaultHeader(Map<String, Object> map) {
super(map);
}
public String getType() {
return this.getString("typ");
}
public T setType(String typ) {
this.setValue("typ", typ);
return this;
}
public String getContentType() {
return this.getString("cty");
}
public T setContentType(String cty) {
this.setValue("cty", cty);
return this;
}
public String getCompressionAlgorithm() {
String alg = this.getString("zip");
if (!Strings.hasText(alg)) {
alg = this.getString("calg");
}
return alg;
}
public T setCompressionAlgorithm(String compressionAlgorithm) {
this.setValue("zip", compressionAlgorithm);
return this;
}
}
playload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
- 标准中注册的声明
- 公共的声明
- 私有的声明
标准中注册的声明 (建议但不强制使用) :
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
定义一个payload:
然后将其进行base64加密,得到Jwt的第二部分
eyJ1c2VOYW1lIjoieWFuZ3lhbmdwaW5nIiwidXNlclR5cGUiOjEsImV4cCI6MTY3ODA3MjcwNSwidXNlcklkIjoxfQ
signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
这个部分需要base64加密后的header和base64加密后的payload使用.
连接组成的字符串,然后通过header中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt的第三部分。
var encodedString = Base64.getEncoder().encodeToString(header) + '.' + Base64.getEncoder().encodeToString(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.
连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VOYW1lIjoieWFuZ3lhbmdwaW5nIiwidXNlclR5cGUiOjEsImV4cCI6MTY3ODA3MjcwNSwidXNlcklkIjoxfQ.crrtA7O7vcSkMPEmqazIbQwf29u1_uqCrBvft6o2Wn8
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
四、使用步骤
1.引入库
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
代码如下(示例):
public class JwtUtil {
/**
* 密钥
*/
private static final String SECRET = "yangyanping@0615.";
private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000;
/**
* 从数据声明生成令牌
*/
public static String generateToken(LoginContext.User user) {
return generateToken(user.getId(), user.getUserType(), user.getName());
}
/**
* 从数据声明生成令牌
*/
public static String generateToken(Long userId, Integer userType, String useName) {
long currentTime = System.currentTimeMillis();
Map<String, Object> claims = Maps.newHashMap();
claims.put("userId", userId);
claims.put("userType", userType);
claims.put("useName", useName);
return Jwts.builder()
.setId(UUID.randomUUID().toString())
//设置签发时间
.setIssuedAt(new Date(currentTime)) //签发时间
//设置body数据
.setClaims(claims)
//签约算法和设置秘钥
.signWith(SignatureAlgorithm.HS256, SECRET) //加密方式
.setExpiration(new Date(currentTime + EXPIRE_TIME)) //过期时间戳
.compact();
}
/**
* 从令牌中获取用户名
*/
public static LoginContext.User getUserFromToken(String token) {
LoginContext.User user;
try {
Claims claims = getClaimsFromToken(token);
if (null == claims) {
return null;
}
Long userId = Long.valueOf(claims.get("userId") + "");
Integer userType = (Integer) claims.get("userType");
String useName = (String) claims.get("useName");
user = new LoginContext.User(userId, userType, useName);
} catch (Exception e) {
user = null;
}
return user;
}
/**
* 判断令牌是否过期
*/
public static Boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token);
if (null == claims) {
return false;
}
Date expiration = claims.getExpiration();
return expiration.before(new Date());
} catch (Exception e) {
return false;
}
}
/**
* 从令牌中获取数据声明
*/
private static Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
}
2.单元测试
代码如下(示例):
public static void main(String[] args) {
String token = JwtUtil.generateToken(1L,1,"yangyangping");
System.out.println("token=" + token);
LoginContext.User user = JwtUtil.getUserFromToken(token);
System.out.println(user.getId() + "," + user.getUserType() +"," + user.getName());
}
测试输出结果:
token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VOYW1lIjoieWFuZ3lhbmdwaW5nIiwidXNlclR5cGUiOjEsImV4cCI6MTY3ODA2ODk3MSwidXNlcklkIjoxfQ.-_Es5ipQxXxMcPa-MHndV7RUHFOuGDYgRg871bllm6U
1,1,yangyangping
3.TokenInterceptor 拦截器
/**
* token登陆拦截器
*
* @author yangyanping
* @date 2023-03-06
*/
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (StringUtils.isBlank(token)) {
return true;
}
try {
Boolean expired = JwtUtil.isTokenExpired(token);
if (!expired) {
Long userId = JwtUtil.getUserIdFromToken(token);
UserInfo userInfo = ApplicationContextHelper.getBean(UserLoginCache.class).get(userId);
if (userInfo != null) {
LoginContext.put(userInfo);
}
}
} catch (Exception ex) {
log.error("LoginInterceptor error !", ex);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
LoginContext.clear();
}
}
4.如何应用
一般是在请求头里加入Authorization
服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:
总结
参考:
什么是 JWT -- JSON WEB TOKEN - 简书