JWT概念
概述
JWT(JSON Web Token)是一种基于JSON在客户端和服务器之间传递声明的令牌格式。它最常见的用途是保存登录态,让客户端在后续请求中证明自己是谁。
在前后端分离、App、小程序、纯API服务里,JWT很常见。用户登录成功以后,服务端签发JWT,客户端保存JWT,后续请求把JWT放到HTTP Header中。
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
服务端收到请求后,只要校验签名和过期时间,就能判断这段Token是否可信。
结构
JWT由三段组成,中间用.分隔。
Header.Payload.Signature
Header描述令牌类型和签名算法:
{
"alg": "HS256",
"typ": "JWT"
}
Payload保存声明,也就是业务需要传递的信息:
{
"sub": "10001",
"username": "zhouzhou",
"role": "USER",
"iat": 1710000000,
"exp": 1710003600
}
Signature是签名,用来证明Header和Payload没有被篡改。只要服务端密钥没有泄露,别人就不能伪造一个合法Token。
常见声明
JWT里有一些标准字段,项目里经常会用到:
| 字段 | 含义 | 说明 |
|---|---|---|
sub |
Subject | 用户主体,一般放用户ID |
iat |
Issued At | 签发时间 |
exp |
Expiration Time | 过期时间 |
iss |
Issuer | 签发方 |
aud |
Audience | 接收方 |
jti |
JWT ID | Token唯一ID |
我一般会把用户ID放到sub,把用户名、租户ID、权限版本作为自定义字段。权限列表要不要放进去,要看项目对权限实时性的要求。
优点
- 天生适合WEB和HTTP Header。
- 不依赖Session,服务端可以保持无状态。
- 使用方便,各个平台都有成熟类库。
- 可以和权限、租户、刷新Token等策略组合使用。
- 适合前后端分离、App、小程序、纯API服务。
无状态是JWT最吸引人的地方。服务端不需要保存每个用户的Session,应用扩容时会简单很多。
缺点
JWT也有自己的代价。它签发出去以后,在过期之前通常都会有效。用户退出登录、管理员封禁账号、用户修改密码以后,如果没有额外机制,旧Token可能仍然能访问接口。
常见解决方式有三种:
- Access Token设置较短有效期。
- 使用Refresh Token刷新登录态。
- 在Token里加入版本号,服务端校验当前版本。
另外,JWT默认只是Base64Url编码加签名,不是加密。任何人拿到JWT都能解码出Payload,所以不要在里面放密码、手机号、身份证、微信session_key等敏感信息。
签名算法
常见签名算法分两类。
HMAC使用同一个密钥签发和校验,典型算法是HS256。它实现简单,适合自己的服务端签发、自己的服务端校验。
RSA或ECDSA使用私钥签发、公钥校验,典型算法是RS256。它适合多个服务共同校验Token的场景,因为校验方只需要拿公钥,不需要拿私钥。
如果是单体或普通前后端分离项目,HS256通常够用。如果是微服务或开放平台,RS256会更容易管理密钥边界。
Java中创建JWT
下面示例使用jjwt,只展示核心思路。
public String createToken(Long userId, String username, SecretKey secretKey) {
Instant now = Instant.now();
return Jwts.builder()
.subject(userId.toString())
.claim("username", username)
.issuedAt(Date.from(now))
.expiration(Date.from(now.plus(Duration.ofHours(1))))
.signWith(secretKey)
.compact();
}
这里的subject就是用户ID,expiration是过期时间。过期时间一定要设置,不要签发永不过期的Token。
解析时反过来:
public Claims parseToken(String token, SecretKey secretKey) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
}
如果签名不正确、Token过期、格式错误,解析过程会抛出异常。业务代码不要忽略这些异常,应该统一返回401。
保存位置
浏览器项目里,JWT可以放在内存、sessionStorage、localStorage或Cookie里。每种方式都有取舍。
| 位置 | 优点 | 问题 |
|---|---|---|
| 内存 | 泄露面小 | 页面刷新后丢失 |
| sessionStorage | 当前标签页可用 | XSS后可能被读取 |
| localStorage | 使用方便 | XSS后可能被读取 |
| Cookie | 浏览器自动携带 | 要处理CSRF和SameSite |
如果是小程序或App,一般会使用平台提供的本地存储能力。无论放在哪里,都要控制Token有效期,并且不要把Refresh Token暴露在不可信环境里太久。
使用建议
JWT适合用来传递“已经认证过的身份”,不适合保存大量业务数据。Token越小越好,字段越稳定越好。
我一般会这样设计:
- Access Token有效期短一些。
- Payload里只放用户ID、租户ID、权限版本等必要字段。
- 权限变化要求实时生效时,从缓存或数据库加载权限。
- 修改密码、封禁账号时递增Token版本号。
- 敏感信息只放在服务端,不放进JWT。
把JWT理解成一张临时通行证会比较准确。它能证明用户身份,但不应该替代完整的用户和权限系统。
作者: 舟哥
链接:https://www.b919p4.com
来源: B919P4
本文原创发布于B919P4,©著作权归作者所有,转载请注明出处,谢谢合作!