JWT(JSON Web Token)是目前流行的跨域认证解决方案,是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
JWT是由头部(header)、载荷(payload)、签证(signature)三段信息构成的,将三段信息文本用"."连接在一起就构成了JWT字符串。
例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
使用在线校验工具(https://jwt.io/)将上述Token进行解码就可以看到数据,如下图所示
JWT的头部承载两部分信息:
(1)声明类型:这里主要是JWT。
(2)声明加密算法:通常直接使用HMAC SHA256。
例如:
{
"alg": "HS256",
"typ": "JWT"
}
alg属性表示签名所使用的算法; JWT签名默认的算法为HMAC SHA256; alg属性值HS256就是HMAC SHA256算法; type属性表示令牌类型,这里是JWT。
载荷是JWT的主体,同样也是一个JSON对象。载荷包含三个部分:
(1)标准中的声明(Registered Claims):一组预定义的声明,不是强制的,但是推荐。
(2)公共的声明:可以添加任意信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息。
(3)私有的声明:提供者和消费者所共同定义的声明,一般不建议存放敏感信息。
JWT的第三部分是一个签证信息,由三部分组成:header(base64后的)、payload(base64后的)、secret(密钥,需要保存好)。
例如:
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
签名用于验证消息再传递过程中有没有被更改,并且对于使用私钥签名的Token还可以验证JWT的发送方是否为它所说的发送方。
secret是保存在服务端的,JWT的签发生成也是在服务端的,secret就是用来进行JWT的签发和验证的,所以secret是服务端的私钥,在任何场景都不应该流露出去。
JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,如下所示。
{
"sub": "1234567890",
"name": "Helen",
"admin": true
}
之后,当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名。
服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。
客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。
此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。
<dependencies>
<!-- JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
/**
* JWT工具类
*/
public class JwtHelper {
//过期时间
private static long tokenExpiration = 24*60*60*1000;
//token签名密钥
private static String tokenSignKey = "123456";
//根据参数生成token
public static String createToken(Long userId, String userName) {
String token = Jwts.builder()
.setSubject("YYGH-USER")
//设置过期时间 30分钟
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
//设置主题信息 用户id和用户名称
.claim("userId", userId)
.claim("userName", userName)
//签名哈希
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}
//根据token字符串得到用户id
public static Long getUserId(String token) {
if(StringUtils.isEmpty(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
Integer userId = (Integer)claims.get("userId");
return userId.longValue();
}
//根据token字符串得到用户名称
public static String getUserName(String token) {
if(StringUtils.isEmpty(token)) return "";
Jws<Claims> claimsJws
= Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String)claims.get("userName");
}
}
写个主函数测试下:
public static void main(String[] args) {
String token = JwtHelper.createToken(1L, "lucy");
System.out.println(token);
System.out.println(JwtHelper.getUserId(token));
System.out.println(JwtHelper.getUserName(token));
}
签发和解析都没问题。