JWT(JSON Web Token)源码分析

Java - JWT的简单介绍和使用
Java JWT:原理、机制及案例示范

什么是JWT?

1.1 JWT的基本概念

JWT(JSON Web Token)是一种用于在各方之间传递JSON格式信息的紧凑、URL安全的令牌(Token)。JWT的主要作用是验证用户身份或权限。它由三部分组成:

  1. Header(头部):标识令牌的类型和加密算法。
  2. Payload(载荷):包含了实际的身份信息及其他数据。
  3. 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;
    }
}
相关推荐
ArabySide20 小时前
【WCF】通过AOP实现基于JWT的授权与鉴权的实践
c#·jwt·aop·wcf
ZHOU_WUYI20 天前
flask JWT 认证
后端·flask·jwt
小小工匠21 天前
每日一博 - JWT 安全实战指南
安全·jwt
FungLeo1 个月前
NodeJS Koa 后端用户会话管理,JWT, Session,长短Token,本文一次性讲明白
jwt·token·session·会话管理·cookies
14L1 个月前
互联网大厂Java面试:从Spring Cloud到Kafka的技术考察
spring boot·redis·spring cloud·kafka·jwt·oauth2·java面试
失败尽是常态Z1 个月前
基于JWT+Redis的登录流程实现
java·数据库·redis·状态模式·jwt·用户登录
Uranus^1 个月前
深入解析Spring Boot与Spring Security整合实现JWT认证
java·spring boot·spring security·认证·jwt
Uranus^1 个月前
使用Spring Boot和Spring Security结合JWT实现安全的RESTful API
java·spring boot·spring security·jwt·restful api
白总Server1 个月前
Golang基于 Swagger + JWT + RBAC 的认证授权中间件设计
linux·运维·服务器·架构·go·bash·jwt