一、JWT简介
a. JWT(JSON Web Token), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
b. 相比于session,它无需保存在服务器,不占用服务器内存开销。
c. 无状态、可拓展性强:比如有3台机器(A、B、C)组成服务器集群,若session存在机器A上,session只能保存在其中一台服务器,此时你便不能访问机器B、C,因为B、C上没有存放该Session,而使用token就能够验证用户请求合法性,并且我再加几台机器也没事,所以可拓展性好就是这个意思。
d. 前后端分离,支持跨域访问。
二、JWT的组成
JWT是由三段信息构成的,将这三段信息文本用.连接一起就构成了JWT字符串。
就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT包含了三部分:
Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型)
Payload 负载 (类似于飞机上承载的物品, 这里指的就是数据本身)
Signature 签名/签证
Header - JWT的头部承载两部分信息:token类型和采用的加密算法。
{
“alg”: “HS256”,
“typ”: “JWT”
}
声明类型:这里是jwt
声明加密的算法:通常直接使用 HMAC SHA256
加密算法是单向函数散列算法,常见的有MD5、SHA、HAMC。
MD5(message-digest algorithm 5) (信息-摘要算法)缩写,广泛用于加密和解密技术,常用于文件校验。不管文件多大,经过MD5后都能生成唯一的MD5值
SHA (Secure Hash Algorithm,安全散列算法),数字签名等密码学应用中重要的工具,安全性高于MD5
HMAC (Hash Message Authentication Code),散列消息鉴别码,基于密钥的Hash算法的认证协议。用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。常用于接口签名验证
Payload - 载荷就是存放有效信息的地方。
有效信息包含三个部分
1.标准中注册的声明
2.公共的声明
3.私有的声明
标准中注册的声明 (建议但不强制使用) :
iss: jwt签发者
sub: 面向的用户(jwt所面向的用户)
aud: 接收jwt的一方
exp: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间)
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
signature - jwt的第三部分是一个签证信息,这个签证信息由三部分组成
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和进行验证,所以需要保护好。
三、SpringBoot整合JWT
- 添加maven依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.2</version>
</dependency>
- 创建JWT工具类或Service类
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator.Builder;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.lang.time.DateUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class JwtUtils {
private static JWTVerifier verifier;
private static final String SECRET = "A_string_of_arbitrary_length|Also_can_be_the_user's_password.";
private static final String ISSUER = "PEGASUS";
private static final String NAME = "JWT_ID";
public static String createToken(String id) {
Map<String, String> payload = new HashMap<>();
payload.put(NAME, id);
return create(payload);
}
public static String analyseToken(String token) {
DecodedJWT decodedJWT = JwtUtils.verify(token);
return decodedJWT.getClaim(NAME).asString();
}
public static String create(Map<String, String> payload) {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("alg", "HS256");
headers.put("typ", "JWT");
Builder builder = JWT.create() // 创建JWT构建器
.withJWTId(UUID.randomUUID().toString()) // 设置JWT的ID
.withHeader(headers) // 设置头部信息
.withIssuer(ISSUER) // 签名的生成者
.withSubject("SUBJECT") // 签名主题
.withNotBefore(new Date()) // 定义在什么时间之前,该JWT都是不可用的
.withAudience("AUDIENCE") // 签名的接受者
.withIssuedAt(new Date()) // 生成签名的时间
.withExpiresAt(DateUtils.addMinutes(new Date(), 10)); // 签名过期时间
payload.forEach((key, value) -> builder.withClaim(key, value));
return builder.sign(algorithm);
}
public static DecodedJWT verify(String token) {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
if (verifier == null) {
verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
}
return verifier.verify(token);
}
}
- 调用测试
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.HashMap;
import java.util.Map;
public class App {
public static void main(String[] args) {
Map<String, String> payload = new HashMap<>();
payload.put("name", "张三");
payload.put("age", "18");
payload.put("sex", "男");
String sign = JwtUtils.create(payload);
System.out.println(sign);
DecodedJWT verify = JwtUtils.verify(sign);
System.out.println(verify.getClaim("name").asString());
System.out.println(verify.getClaim("age").asString());
System.out.println(verify.getClaim("sex").asString());
}
}