什么是 Token
Token(令牌)
是一种用于身份验证和授权的机制,它是在用户登录后生成的一段字符串,用于识别用户的身份信息,以便在用户进行后续操作时进行身份验证和授权
Token
是服务端生成的一串字符串,用来作为客户端的请求令牌。当用户首次登录成功后,服务端会生成一个token
并返回给客户端,此后客户端只需要带上token
字段,而无需再上传用户名和密码;但当token
过期后,用户又会回到首次登录的状态,需要输入用户名和密码
什么是 JWT
Token
的生成和解析通常采用JWT(JSON Web Token)协议
,它是一种轻量级的身份验证和授权机制。JWT
由三部分组成:头部(Header)
、载荷(Payload)
和签名(Signature)
,其中头部和载荷都是使用Base64 编码
的JSON 对象
,签名是由头部、载荷和密钥生成的哈希值
JWT 的生成过程
Spring Boot 采用了标准的 JWT 头部格式, 如下:
{
"alg": "HS256",
"typ": "JWT"
}
因此默认情况下使用的是 SignatureAlgorithm.HS256 对称加密算法
但也支持其他的签名算法,比如:SignatureAlgorithm.RS256
非对称加密算法
需要在 application.properties 或 application.yml 文件中通过对应的属性进行配置---spring.security.jwt.signature-algorithm。例如:如果需要使用 RS256 算法,则可以设置该属性为 RS256
JWT 生成 token 的代码
private static final String SECRET = "secret_key";
private static final long EXPIRATION_TIME = 3600000;
public static String createJwtToken(String userId) {
Map claims = new HashMap();
claims.put("userId", userId);
long timeMillis = System.currentTimeMillis();
Date expirationDate = new Date(timeMillis + EXPIRATION_TIME);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
配置文件里面配置通用变量
# SECRET 和 EXPIRATION_TIME 也可以在 application.yml 文件中进行配置,这样就可以通用了
spring:
security:
jwt:
secret: secret_key
expires: 86400000 # 过期时间,单位为毫秒,这里设置的过期时间为1天
使用配置文件里面配置的通用变量
// 在代码中获取配置文件的值
@Value("${spring.security.jwt.secret}")
private static String SECRET;
@Value("${spring.security.jwt.expires}")
private static long EXPIRATION_TIME;
方法解析
builder
builder: 用于创建 JWT 字符串的静态方法,它是由 Jwts 类提供的。该方法返回一个 JwtBuilder 对象,用于构建JWT并生成 JWT 字符串
setClaims
setClaims: 用于设置载荷部分的方法。在 JWT 中,载荷是一个JSON 对象
,它包含了一些与用户身份验证和授权相关的信息,例如用户ID、用户名、角色、权限等
// 用法一
claims.put("userId", userId);
return Jwts.builder()
.setClaims(claims)
.compact();
// 用法二
return Jwts.builder()
.claims("userId", userId)
.claims("username", username)
.compact();
setIssuedAt
setIssuedAt: 用于设置签发时间(issued at)的方法。在 JWT 中,签发时间是一个整数,表示 JWT 的生成时间,通常以秒为单位
setExpiration
setExpiration: 用于设置过期时间(expiration)的方法。在 JWT 中,过期时间也是一个整数,表示 JWT 的有效期,通常以秒为单位
注意:设置签发时间和过期时间的方法传入的时间都是 Date 对象,但 setIssuedAt、setExpiration 会将这个 Date 对象转换为 Unix 时间戳并编码到 JWT 中
生成时间的多种方法:
-
System.currentTimeMillis(): 获取当前时间的毫秒数,类型为 long, 从而得到时间戳(单位为秒)
-
new Date(): 返回当前时间的 Date 对象,包含当前时间的年、月、日、时、分、秒等信息
这个方法通常用于获取当前时间的具体信息或用于时间格式化 -
getTime(): Date 对象的方法,用于获取当前时间的时间戳
-
new Date(时间戳): 生成的是一个表示当前时间戳的 Date 对象
signWith
signWith: 用于设置签名的方法,可以使用不同的算法来对 JWT 进行签名。JWT 使用签名来保证 JWT 的真实性和完整性,防止 JWT 被篡改或伪造
signWith 方法接受两个参数,分别是签名算法和密钥
签名算法的类型必须要和 JWT 头部中的 “alg” 字段一致,否则验证时会提示签名无效
因此,在使用JWT进行签名时,必须要明确指定签名算法
签名算法是由 SignatureAlgorithm 枚举类提供的,它定义了一系列的签名算法,包括 HMAC 和 RSA
在 signWith 方法中: 如果为对称算法,则密钥应该是一个 byte[] 数组或 String 类型的字符串;
如果为非对称算法,则密钥应该是一个 Key 类型的对象,例如 RSAPrivateKey 或 RSAPublicKey
对称加密算法,需要使用相同的密钥进行加解密
非对称加密算法,使用公钥进行加密,私钥进行解密
SignatureAlgorithm.HS512 算法,属于 HMAC-SHA512 对称加密算法
SignatureAlgorithm.HS256 算法,属于 HMAC-SHA256 对称加密算法
SignatureAlgorithm.RS256 算法, 属于 RSA-SHA256 非对称加密算法
compact
compact: 用于将 JWT 编码为紧凑的字符串形式。即将 JWT 编码为没有空格、没有等号、没有加号、没有斜杠的紧凑字符串形式,以便在 URL 中传输
方法总览
方法 | 含义 |
---|---|
Jwts.builder() | 创建一个 JwtBuilder 对象 |
JwtBuilder.setClaims() | 设置 JWT 的声明(claims) |
JwtBuilder.setIssuer() | 设置 JWT 的发行者(issuer) |
JwtBuilder.setSubject() | 设置 JWT 的主题(subject) |
JwtBuilder.setAudience() | 设置 JWT 的受众(audience) |
JwtBuilder.setIssuedAt() | 设置 JWT 的签发时间(issuedAt) |
JwtBuilder.setExpiration() | 设置 JWT 的过期时间(expiration) |
JwtBuilder.signWith() | 设置 JWT 的签名算法和密钥 |
JwtBuilder.compact() | 生成 JWT 字符串 |
JWT 的解析过程
后端获取 token 的方式通常有两种:
-
通过 HTTP 请求头获取:前端在请求时将 token 放置于 HTTP 请求头中,通常是以
Authorization: Bearer
的形式,后端可以通过读取请求头中的Authorization
字段获取 token -
通过请求参数获取:前端在请求时将 token 以某个参数名的形式传递,例如:
token=
,后端可以通过解析请求参数获取 token
一般来说,第一种方式更为常用和安全,因为 HTTP 请求头中的内容不会被浏览器缓存或保存在历史记录中,而请求参数则可能会被保存。因此,建议在实现后端获取 token 的逻辑时,优先考虑使用 HTTP 请求头方式
private static final String SECRET = "secret_key";
public static Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
方法解析
parser
parser: 用于解析 JWT 字符串的静态方法,它是由 Jwts 类提供的。该方法接受一个 JWT 字符串,并返回一个 JwtParser 对象,用于解析 JWT 字符串并验证其签名
setSigningKey
setSigningKey: 用于设置 JWT 签名密钥的方法,它是由 JwtBuilder 类提供的。该方法接受一个 Key 类型的参数,表示用于签名的密钥
在生成 JWT 时,需要将密钥传递给 signWith()方法以指定签名算法和密钥
在验证 JWT 时,也需要将密钥传递给 setSigningKey()方法以验证签名
签名密钥可以是对称密钥(例如HMAC密钥)或非对称密钥(例如RSA密钥对)
对称密钥适用于需要高性能和低成本的场景
非对称密钥适用于需要更高的安全性和可扩展性的场景
parseClaimsJws
parseClaimsJws: 是 JWT 解析方法,用于解析 JWT Token 并获取其中的载荷信息(payload),同时也会验证签名是否正确
getBody
getBody: 用于从 JWT 中获取载荷(payload)信息。调用此方法将返回一个 Claims 对象,其中包含了 JWT 载荷中的所有声明信息,可以通过 get 方法获取对应的声明值
方法总览
方法 | 含义 |
---|---|
Jwts.parser() | 创建一个 JwtParser 对象 |
JwtParser.setSigningKey() | 设置 JWT 的签名密钥 |
JwtParser.parseClaimsJws() | 解析 JWT 字符串并返回一个 Claims 对象,包含了 JWT 中所有的声明(claims) |