前言
JWT主要是用于用户登录鉴权,传统的方法就是session认证。
http是无状态的协议,当有用户向系统使用账户名称和密码进行用户认证之后,下一次请求还要再一次用户认证才行。因为我们不能通过http协议知道是哪个用户发出的请求,所以如果要知道是哪个用户发出的请求,那就需要在服务器保存一份用户信息(保存至session),然后在认证成功后返回cookie值传递给浏览器,那么用户在下一次请求时就可以带上cookie值,服务器就可以识别是哪个用户发送的请求,是否已认证,是否登录过期等等。
session认证的缺点其实很明显,由于session是保存在服务器里,所以如果分布式部署应用的话,会出现session不能共享的问题,很难扩展。
JWT的介绍
JWT:Json Web Token 它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
流程:
- 用户使用账号、密码登录应用,登录的请求发送到Authentication Server。
- Authentication Server进行用户验证,然后创建JWT字符串返回给客户端。
- 客户端请求接口时,在请求头带上JWT。
- Application Server验证JWT合法性,如果合法则继续调用应用接口返回结果
用户信息是保存在客户端,关键在于生成JWT和解析JWT。
JWT的数据结构
JWT一般是这样的字符串,分为三个部分,以"."隔开。
xxxxx.yyyyy.zzzzz
Header:第一部分是头部分,描述JWT元数据的Json对象
{
"alg": "HS256",
"typ": "JWT"
}
alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256),typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。
Payload:第二部分是Payload,也是一个Json对象,除了包含需要传递的数据,还有七个默认的字段供选择。
分别是,iss:发行人、exp:到期时间、sub:主题、aud:用户、nbf:在此之前不可用、iat:发布时间、jti:JWT ID用于标识该JWT。
如果自定义字段,可以这样定义:
{
//默认字段
"sub":"主题123",
//自定义字段
"name":"java技术爱好者",
"isAdmin":"true",
"loginTime":"2021-12-05 12:00:03"
}
JSON对象也使用Base64 URL算法转换为字符串保存。
Signature:第三部分是签名。是这样生成的,首先需要指定一个secret,该secret仅仅保存在服务器中,保证不能让其他用户知道。然后使用Header指定的算法对Header和Payload进行计算,然后就得出一个签名哈希。
JWT的优点:
- json格式的通用性,所以JWT可以跨语言支持,比如Java、JavaScript、PHP、Node等等。
- 可以利用Payload存储一些非敏感的信息。
- 便于传输,JWT结构简单,字节占用小。
- 不需要在服务端保存会话信息,易于应用的扩展。
使用JWT
导入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
创建工具类,用于创建jwt字符串和解析jwt
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import java.util.Date;
/**
*
* @description JWT:令牌token生成
*/
public class TokenUtil {
// token有效期
private static final long EXPIRATION = 86400L;//1day = 86400L
/**
*
* @description 创建token工具方法
*/
public static String createToken(User user) {
JwtBuilder builder = Jwts.builder();
builder.setAudience(user.getUserCode()) // 这个user.getUserCode()是我后续需要的参数
.setIssuer("xxx")
.claim("userId", user.getId()) // userId 是我自己后续需要的参数,这个根据自己的需求进行设置
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000));
String accessToken = builder.compact();
return accessToken;
}
/**
*
* @description 验证token返回用户code
*/
public static String validateToken(String token) {
Claims claims = Jwts.parserBuilder().build().parseClaimsJwt(token).getBody();
String userCode = claims.getAudience();
return userCode;
}
/**
*
* @description 验证token返回用户ID
*/
public static String getUserIdFromToken(String token) {
Claims claims = Jwts.parserBuilder().build().parseClaimsJwt(token).getBody();
String userId = claims.get("userId", String.class);
return userId;
}
}
有些微修改,我这里没有使用到第三部分。