JWT(JSON Web Token)完全指南
目录
JWT的结构
Header
Payload
Signature
JWT的特点
JWT(JSON Web Token)是一种开放标准,用在各方之间安全地传输紧凑且可验证的结构化信息。它是URL安全的令牌,核心价值是”通过签名确保信息未被篡改、来源可信“,数据本身仅通过Base64URL编码改造,不提供加密保护。
JWT的结构
JWT由三部分组成,用点(.)分割,分别是:
- Header(头部):包含令牌类型和签名算法;
- Payload(载荷):包含声明;
- Signature(签名):用于验证令牌的完整性;
Header
Header部分通常由两部分组成:令牌的类型(typ)和所使用的签名算法(alg),是一串JSON格式的字符串,再经过base64Url编码形成字符串。
- typ:令牌类型,通常为JWT;让接收方识别是哪种类型的令牌。
- alg:签名算法,可以为:HS256,RS256,ES256,HS384等;生产jwt签名时使用的算法。
- kid:密钥id;
- cty:内容类型;
Payload
Payload是核心数据载体,用于存储业务数据(用户身份、权限)和元数据(过期时间,签发者)。本质是一个JSON对象,通过Base64Url编码后成为Payload,核心特点是可解码但不可篡改。
Payload通常包含以下声明:
标准声明:不强制但强烈推荐
- exp:JWT过期时间的时间戳。
- nbf:JWT生效时间的时间戳。
- iss:JWT的签发者。
- aud:JWT的目标受众。
- sub:声明JWT的主题
公共声明:用户自定义的,可在不同系统间共享的声明。JWT规范要求键名须在IANA JSON Web Token Claims注册表中注册,或者包含域名的键名避免重复。
私有声明:系统内部使用的自定义声明,注意避免重复
Signature
它用于验证令牌在传输的过程中是否被篡改,在使用非对称算法的情况还可以验证发送方的身份。
通过将Header与payload经过base64url编码后的结果用点连接起来,再使用header中指定的签名算法和密钥进行签名,最后结果base64url编码形成jwt的最后一部分。
JWT的特点
优点:
- 体积小,传播速度快;
- 支持跨域传递;
- 令牌中包含所有必要的用户信息;
- 无状态,无需存储token信息,仅通过token即可验证。
缺点:
- JWT相对较长,可能影响网络传输;
- JWT不能存储敏感信息;
- 一次性,如果想要修改里面的内容,需重新发一个JWT;
JWT的生成流程
package org.example.util;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;public class JwtUtil {// 设置JWT的过期时间为24小时public static final long JWT_TOKEN_VALIDITY = 24 * 60 * 60 * 1000;// 生成签名密钥,实际情况应该是手动指定的密钥或从配置文件中读取private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);//可以手动指定密钥/*** 生成JWT令牌* @param username 用户名* @return JWT令牌*/public static String generateToken(Map<String,Object> claims,String username) {return doGenerateToken(claims, username);}/*** 生成访问token*/public static String generateToken(String username) {return doGenerateToken(new HashMap<>(), username);}/*** 执行令牌生成* @param claims 声明* @param subject 主题(用户名)* @return JWT令牌*/private static String doGenerateToken(Map<String, Object> claims, String subject) {return Jwts.builder().setClaims(claims) //自定义声明.setSubject(subject) //设置主题.setIssuedAt(new Date(System.currentTimeMillis())) //设置签发时间.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY)) //设置过期时间.signWith(SECRET_KEY) //设置签名密钥.compact(); //生成令牌}/*** 验证JWT令牌* @param token JWT令牌* @param username 用户名* @return 是否有效*/public static Boolean validateToken(String token, String username) {final String extractedUsername = extractUsername(token);return (extractedUsername.equals(username) && !isTokenExpired(token));}/*** 从JWT令牌中提取用户名* @param token JWT令牌* @return 用户名*/public static String extractUsername(String token) {return extractAllClaims(token).getSubject();}/*** 检查JWT令牌是否过期* @param token JWT令牌* @return 是否过期*/private static Boolean isTokenExpired(String token) {return extractExpiration(token).before(new Date());}/*** 从JWT令牌中提取过期时间* @param token JWT令牌* @return 过期时间*/public static Date extractExpiration(String token) {return extractAllClaims(token).getExpiration();}/*** 从JWT令牌中提取所有声明* @param token JWT令牌* @return 声明*/private static Claims extractAllClaims(String token) {return Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token).getBody();}
}
在验证的方法extractAllClamins中:
- setSigningKey设置验证密钥,parseClaimsJws会自动验证签名,如果无效就抛出异常。
JWT的工作流程
- 用户登录:用户提供凭据(用户名/密码)进行身份验证
- 服务器验证:服务器验证凭据,如果正确则生成JWT
- 返回JWT:服务器将JWT返回给客户端
- 存储JWT:客户端存储JWT(通常是localStorage或cookie)
- 发送JWT:后续请求中,客户端在Authorization头中携带JWT
- 验证JWT:服务器验证JWT的有效性
- 响应请求:如果JWT有效,服务器处理请求并返回响应