1. 介绍
JWT (JSON Web Token) 是目前最流行的跨域认证解决方案,是一种基于 Token 的认证授权机制。 从 JWT 的全称可以看出,JWT 本身也是 Token,一种规范化之后的 JSON 结构的 Token。
JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
2.JWT的组成
1.标头(Header)
2.有效载荷(Payload)
3.签名(Signature)
Header部分:
{
"alg": "HS256",
"typ": "JWT"
}
该字段为Json格式,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256),
typ属性表示令牌的类型,JWT令牌统一写为JWT(默认也为JWT)。
JSON 形式的 Header 被转换成 Base64 编码,成为 JWT 的第一部分。
Payload部分:
Payload 也是 JSON 格式数据,其中包含了 Claims(声明,包含 JWT 的相关信息)。
Claims 分为三种类型:
Registered Claims(注册声明) :预定义的一些声明,建议使用,但不是强制性的,可互操作的声明。
Public Claims(公有声明) :JWT 签发方可以自定义的声明,但是为了避免冲突,应该在 IANA JSON Web Token Registry 中定义它们。
Private Claims(私有声明) :JWT 签发方因为项目需要而自定义的声明,更符合实际项目场景使用。
下面是一些常见的注册声明:
iss
(issuer):JWT 签发方。
iat
(issued at time):JWT 签发时间。
sub
(subject):JWT 主题。
aud
(audience):JWT 接收方。
exp
(expiration time):JWT 的过期时间。
nbf
(not before time):JWT 生效时间,早于该定义的时间的 JWT 不能被接受处理。
jti
(JWT ID):JWT 唯一标识。
{
"uid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
"sub": "1234567890",
"name": "John Doe",
"exp": 15323232,
"iat": 1516239022,
"scope": ["admin", "user"]
}
Payload 部分默认是不加密的,一定不要将隐私信息存放在 Payload 当中!!!
JSON 形式的 Payload 被转换成 Base64 编码,成为 JWT 的第二部分。
Signature部分
Signature 部分是对前两部分的签名,作用是防止 JWT(主要是 payload) 被篡改。
这个签名的生成需要用到:
- Header(加密)+ Payload(加密)。
- 存放在服务端的密钥(不能泄露)。
- 签名算法。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,这个字符串就是 JWT 。
3.生成,解析Token代码
private static long time = 1000*60*60*24;
private static String signature = "admin"; //加密 ,解密需要
/**
* setHeaderParam -> 设置 Header头信息
* claim -> 携带的数据
* setSubject -> 设置主机
* setExpiration -> 保存时间
* setId -> 设置ID
* signWith -> 声明算法和签名
* compact -> 拼接
*/
@Test
void contextLoads() {
JwtBuilder builder = Jwts.builder();
final String token = builder
//header
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
//保存用户名,以及角色,注意这里最好保存非私密信息,比如密码最好不要保存
.claim("username", "admin")
.claim("role", "admin")
.setSubject("admin-test")
//设置过期时间
.setExpiration(new Date(System.currentTimeMillis() + time))
.setId(UUID.randomUUID().toString())
// 设置算法及密钥 java通过signature生成签名,解密也需要signature
.signWith(SignatureAlgorithm.HS256, signature)
.compact();
System.out.println(token);
}
@Test
void parse() {
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" +
".eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwic3ViIjoiYWRtaW4tdGVzdCIsImV4cCI6MTY" +
"2NjYxNTM5NSwianRpIjoiOGMyZTRiZGEtYTI5Mi00NzE3LThlZjYtYjExNTI0N2ZkMzljIn0" +
".XLF9HPezm4w6b8LIloQz_pDemshAp0ublOfbmft2nbA";
JwtParser jwtParser = Jwts.parser();
//通过该签名对token进行解析,解析成为多个claim
Jws<Claims> claimsJws = jwtParser.setSigningKey(signature_key).parseClaimsJws(token);
System.out.println(claimsJws.toString());
Claims claims = claimsJws.getBody();
System.out.println("用户名 "+claims.get("username"));
System.out.println("角色信息 "+claims.get("role"));
}
}
4.token在用户登录时的应用
token都是在用户登录时生成,并且进行判断,进行更新,下图简略的概要token生成,以及更新。