Java - JWT的简单介绍和使用
Java JWT:原理、机制及案例示范
什么是JWT?
1.1 JWT的基本概念
JWT(JSON Web Token)是一种用于在各方之间传递JSON格式信息的紧凑、URL安全的令牌(Token)。JWT的主要作用是验证用户身份或权限。它由三部分组成:
- Header(头部):标识令牌的类型和加密算法。
- Payload(载荷):包含了实际的身份信息及其他数据。
- Signature(签名):使用头部和载荷生成的签名,用于验证数据完整性和来源的可靠性。
1.2 JWT的结构

JWT的结构由三部分组成,它们通过点号(.)进行分隔,格式如下:
Header.Payload.Signature
具体每一部分的内容如下:
-
Header(头部): 通常包含两部分信息:令牌的类型(JWT)和签名的算法(如HMAC SHA256或RSA)。
{
"alg": "HS256",
"typ": "JWT"
} -
Payload(载荷): 载荷是JWT的主体部分,包含了需要传输的数据。通常包含以下几种常见的声明(Claims)
iss:签发者
exp:过期时间
sub:主题
aud:接收者
iat:签发时间
nbf:在此之前不可用 -
Signature(签名): 签名部分是用来验证消息的完整性,并确保其未被篡改。签名的生成方式如下:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret)
代码示例
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.1</version>
</dependency>
java
package tool;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.junit.Test;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
public class JwtToolTest {
// 秘钥,你可以随便取,可以取的难一点
public static final String SECRET = "ASD!@#F^%A";
//加密
private static String tokenCreate() {
HashMap<String, Object> headers = new HashMap<>();
// 过期时间,60s
Calendar expires = Calendar.getInstance();
expires.add(Calendar.SECOND, 600);
return JWT.create()
// 第一部分Header
.withHeader(headers)
// 第二部分Payload
.withClaim("userId", 20)
.withClaim("userName", "LJJ")
//相同的key, 会覆盖前面的数据
.withClaim("userName", List.of("aaa", "bb"))
.withSubject("hahahha")
.withExpiresAt(expires.getTime())
// 第三部分Signature
.sign(Algorithm.HMAC256(SECRET));
}
@Test
public void testTokenCreate() {
System.out.println(tokenCreate());
}
//解密
@Test
public void testReadJWT() {
// 创建一个验证的对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
DecodedJWT verify = jwtVerifier.verify(tokenCreate());
System.out.println(verify.getClaim("userId").asInt());
System.out.println(verify.getClaim("userName"));
System.out.println(verify.getSubject());
System.out.println("过期时间:" + verify.getExpiresAt());
}
}
JWTCreator.java
JWT.create() 会创建 JWTCreator
java
public static JWTCreator.Builder create() {
return JWTCreator.init();
}
static JWTCreator.Builder init() {
return new Builder();
}
Builder() {
this.payloadClaims = new HashMap<>();
this.headerClaims = new HashMap<>();
}
初始化两个map ,用来存储header 和 payload数据
提供的with方法最终都是往 payloadClaims 、 headerClaims 中添加key:value数据
特殊的key数据
java
public interface PublicClaims {
//Header
String ALGORITHM = "alg";
String CONTENT_TYPE = "cty";
String TYPE = "typ";
String KEY_ID = "kid";
//Payload
String ISSUER = "iss";
String SUBJECT = "sub";
String EXPIRES_AT = "exp";
String NOT_BEFORE = "nbf";
String ISSUED_AT = "iat";
String JWT_ID = "jti";
String AUDIENCE = "aud";
}
加密
java
private String sign() throws SignatureGenerationException {
String header = Base64.getUrlEncoder().withoutPadding().encodeToString(headerJson.getBytes(StandardCharsets.UTF_8));
String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getUrlEncoder().withoutPadding().encodeToString((signatureBytes));
return String.format("%s.%s.%s", header, payload, signature);
}
verify(解密校验)
java
// JWTVerifier.java
@Override
public DecodedJWT verify(String token) throws JWTVerificationException {
DecodedJWT jwt = new JWTDecoder(parser, token);
return verify(jwt);
}
@Override
public DecodedJWT verify(DecodedJWT jwt) throws JWTVerificationException {
verifyAlgorithm(jwt, algorithm);
algorithm.verify(jwt);
verifyClaims(jwt, claims);
return jwt;
}
private void verifyClaims(DecodedJWT jwt, Map<String, Object> claims) throws TokenExpiredException, InvalidClaimException {
for (Map.Entry<String, Object> entry : claims.entrySet()) {
if (entry.getValue() instanceof NonEmptyClaim) {
assertClaimPresent(jwt.getClaim(entry.getKey()), entry.getKey());
} else {
verifyClaimValues(jwt, entry);
}
}
}
最终根据特殊的key来校验token
java
private void verifyClaimValues(DecodedJWT jwt, Map.Entry<String, Object> expectedClaim) {
switch (expectedClaim.getKey()) {
// We use custom keys for audience in the expected claims to differentiate between validating that the audience
// contains all expected values, or validating that the audience contains at least one of the expected values.
case AUDIENCE_EXACT:
assertValidAudienceClaim(jwt.getAudience(), (List<String>) expectedClaim.getValue(), true);
break;
case AUDIENCE_CONTAINS:
assertValidAudienceClaim(jwt.getAudience(), (List<String>) expectedClaim.getValue(), false);
break;
case PublicClaims.EXPIRES_AT:
assertValidDateClaim(jwt.getExpiresAt(), (Long) expectedClaim.getValue(), true);
break;
case PublicClaims.ISSUED_AT:
assertValidDateClaim(jwt.getIssuedAt(), (Long) expectedClaim.getValue(), false);
break;
case PublicClaims.NOT_BEFORE:
assertValidDateClaim(jwt.getNotBefore(), (Long) expectedClaim.getValue(), false);
break;
case PublicClaims.ISSUER:
assertValidIssuerClaim(jwt.getIssuer(), (List<String>) expectedClaim.getValue());
break;
case PublicClaims.JWT_ID:
assertValidStringClaim(expectedClaim.getKey(), jwt.getId(), (String) expectedClaim.getValue());
break;
case PublicClaims.SUBJECT:
assertValidStringClaim(expectedClaim.getKey(), jwt.getSubject(), (String) expectedClaim.getValue());
break;
default:
assertValidClaim(jwt.getClaim(expectedClaim.getKey()), expectedClaim.getKey(), expectedClaim.getValue());
break;
}
}