JWT是什么
JWT(JSON Web Token)是一种用于进行身份验证和授权的开放标准,是基于JSON(JavaScript Object Notation)的一种轻量级的身份验证和授权机制。JWT使用一种紧凑且自包含的格式,将声明(claims)安全地传输于不同的应用之间。
JWT的结构
Header(头部)
- 签名算法:声明签名所用的算法
- 令牌类型:告诉接收方此令牌是JWT
Payload(负载)
1.注册声明(Registered Claims)
一组预定义的声明,非强制的,但是推荐使用。包括:
iss
(Issuer):签发人exp
(Expiration Time):过期时间sub
(Subject):主题aud
(Audience):受众nbf
(Not Before):生效时间iat
(Issued At):签发时间jti
(JWT ID):JWT的唯一标识
2.共有声明(Public Claims)
携带公共的数据,例如:用户状态、用户权限信息等
3.私有声明(Private Claims)
携带私有的数据,例如用户Id、临时授权码等
Signature(签名)
作用
-
防止篡改:签名可以确保JWT在传输过程中没有被篡改。如果攻击者试图修改JWT中的任何部分(包括头部、负载或签名本身),接收方在验证签名时将会发现不一致,从而拒绝该令牌。
-
验证发送者身份:当使用私钥进行签名时,只有持有对应公钥的一方才能验证签名的有效性。因此,签名可以用于验证发送者的身份。这是一种基于非对称加密的认证机制,确保只有持有私钥的合法发送者才能生成有效的JWT。
-
确保数据来源的可信性:签名提供了一种机制,使得接收方可以确信JWT是由一个可信的发送方生成的,而不是由一个恶意的第三方伪造的。
JWT使用
采用的Java 17,如果用的是Java 8或以下可以用 jjwt 依赖
Maven依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
具体实现
package org.example.demo.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.*;
@Component
public class TokenService {
private static final Logger log = LoggerFactory.getLogger(TokenService.class);
// 令牌自定义标识
private final String header = "Authorization";
// 令牌秘钥
private final String secret = "ASO%#IB43hojas347-#^)&*^sabdjiDGgoui@#8^DF89sa68";
// 令牌有效期(默认30分钟)
private final int expireTime = 30;
protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
private final String userId = "userId";
// 加密算法
String encodedSecret = Base64.getEncoder().encodeToString(secret.getBytes());
/**
* 生成令牌
*
* @param userId 用户Id
* @return JWT 字符串
*/
public String createToken(Long userId) {
// 头部
Map<String, Object> header = new HashMap<>();
header.put("typ", "JWT");
header.put("alg", "HS256");
// 私有声明
Map<String, Object> claims = new HashMap<>();
claims.put(this.userId, userId);
return Jwts.builder()
.setHeader(header) // 设置头部数据
.setIssuer("example.com") // 设置签发者(注册声明)
.setExpiration(new Date(System.currentTimeMillis() + expireTime * MILLIS_MINUTE)) // 设置过期时间(注册声明)
.setSubject("User Token") // 设置主题(注册声明)
.setAudience("all") // 设置接收者(注册声明)
.setNotBefore(new Date(System.currentTimeMillis())) // 设置生效时间(注册声明)
.setIssuedAt(new Date()) // 设置签发时间(注册声明)
.setId(UUID.randomUUID().toString()) // 设置JWT ID(注册声明)
.setClaims(claims) // 设置私有声明
.claim("https://baiodu.com/roles", "admin") // 添加公共声明
.signWith(Keys.hmacShaKeyFor(encodedSecret.getBytes()), SignatureAlgorithm.HS256)
.compact();
}
/**
* 从token中解析UserId
*
* @return UserId
*/
public Long getUserId() {
String token = getToken();
if (token == null) {
throw new RuntimeException("未携带token!或头属性名不为:" + header);
}
final Claims claims = parseToken(token);
try {
return Long.parseLong(String.valueOf(claims.get(userId)));
} catch (NumberFormatException e) {
throw new RuntimeException("解析用户ID失败", e);
}
}
/**
* 解析 JWT
*
* @param token JWT字符串
* @return 数据声明
*/
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(encodedSecret.getBytes())
.build()
.parseClaimsJws(token)
.getBody();
}
/**
* 从请求头中获取token
*
* @return token or null
*/
public String getToken() {
final ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
throw new RuntimeException("无法从当前线程获取请求属性!");
}
final HttpServletRequest request = attributes.getRequest();
return request.getHeader(header);
}
}
可能遇到的异常
解决方案
JWT报错 javax/xml/bind/DatatypeConverter解决办法_javax.xml.bind.datatypeconverter-CSDN博客