当我们在设计 api 接口时,都会给到客户端一个凭证,凭证的实现有很多,比如 cookie-session、jwt 等;jwt 对比其他的方式一个显著特点就是他是分布式的,即不依赖于某一台 server 的实现(存储),所以当前在 RestFul Api 都几乎用 jwt 做凭证(当然也可以用他的变种,比如传输中对 jwt token 进行编码加密等)。
这里我们不讲述 jwt 的具体结构,后面有空的话,再单独写一篇,本篇只讲述 Spring boot 集成 jwt,以及基本用法。
1. JWT 简介
1.1 JWT 组成
JWT: JSON Web Token
jwt 由三部分组成:Header.Payload.Signature
jwt 格式样式: xxxx.yyyy.zzzz
- Header: 描述 JWT 的元数据,定义了生成签名的算法以及 Token 的类型;
- Payload : 用来存放实际需要传递的数据;
- Signature(签名) :服务器通过 Payload、Header 和一个密钥(Secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。
Header
Header 通常由两部分组成:
- typ(Type):令牌类型,也就是 JWT。
- alg(Algorithm) :签名算法,比如 HS256。
比如:
{
"alg": "HS256",
"typ": "JWT"
}
JSON 形式的 Header 被转换成 Base64 编码,成为 JWT 的第一部分。
Payload
Payload 也是 JSON 格式数据,其中包含了 Claims(声明,包含 JWT 的相关信息)。
Claims 分为三种类型:
- Registered Claims(注册声明) :预定义的一些声明,建议使用,但不是强制性的。
- Public Claims(公有声明) :JWT 签发方可以自定义的声明。
- Private Claims(私有声明) :JWT 签发方因为项目需要而自定义的声明,更符合实际项目场景使用。
Payload 部分默认是不加密的,一定不要将隐私信息存放在 Payload 当中!!!
JSON 形式的 Payload 被转换成 Base64 编码,成为 JWT 的第二部分。
Signature
Signature 部分是对前两部分的签名,作用是防止 JWT(主要是 payload) 被篡改。
这个签名的生成需要用到:
- Header + Payload。
- 存放在服务端的密钥(一定不要泄露出去)。
- 签名算法。
计算公式如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,这个字符串就是 JWT 。
2. 添加依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.0.0</version>
</dependency>
3. 代码编写
package org.example.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.xml.crypto.Data;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "jwt")
public class JWTUtils {
private String alg;
private String typ;
private String secret;
private String header;
private Integer expireTime;
public void setHeader(String header) {
this.header = header;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public Integer getExpireTime() {
return expireTime;
}
public void setExpireTime(Integer expireTime) {
this.expireTime = expireTime;
}
public String generate(Map<String, Object> playout) {
System.out.println(header);
Algorithm algorithm = Algorithm.HMAC256(this.secret);
Map<String, Object> headerMap = new HashMap<>();
headerMap.put("alg", this.alg);
headerMap.put("type", this.typ);
return JWT.create()
.withHeader(headerMap)
.withPayload(playout)
.withClaim("xxxx", "1234")
.sign(algorithm);
}
public Map<String, Object> parse(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(this.secret);
Map<String, Claim> claimMap = JWT.require(algorithm).build().verify(token).getClaims();
System.out.println(claimMap.get("xxxx"));
System.out.println(claimMap.get("abc"));
System.out.println(claimMap.get("abc23"));
DecodedJWT j = JWT.decode(token);
String p = JWT.require(Algorithm.HMAC256(this.secret))
.build()
.verify(token)
.getPayload();
System.out.println(p);
} catch (TokenExpiredException e) {
System.out.println("token 过期");
} catch (SignatureVerificationException e) {
System.out.println("token 验证失败");
}
return null;
}
@Override
public String toString() {
return "JWTUtils{" +
"secret='" + secret + '\'' +
", header='" + header + '\'' +
", expireTime=" + expireTime +
'}';
}
public String getAlg() {
return alg;
}
public void setAlg(String alg) {
this.alg = alg;
}
public String getTyp() {
return typ;
}
public void setTyp(String typ) {
this.typ = typ;
}
}