JWT 令牌概述
JWT,即 JSON Web Token(官网:https://jwt.io/),是一种轻量级的、自包含的信息传输格式。它允许在通信双方之间以安全的方式传递 JSON 格式的数据。由于具备数字签名机制,该数据在传输过程中具有较高的可靠性与完整性。
简洁性: JWT 实质上是一个紧凑的字符串,可直接通过请求参数或请求头进行传递,使用便捷。
自包含性: 虽然 JWT 看似一串随机字符,但实际上其内部可以嵌入用户自定义的信息。例如,可以在令牌中直接存储用户 ID、用户名等身份信息,无需额外查询数据库。
简而言之,JWT 是对原始 JSON 数据进行安全封装后的产物,使得信息可以在不同系统间安全流转。
JWT 的结构组成
一个标准的 JWT 由三个部分构成,各部分之间使用英文句点(.)分隔:
- Header(头部): 包含令牌类型(如 JWT)和所使用的签名算法(如 HMAC SHA256 或 RSA)。示例内容如下:
{ "alg": "HS256", "type": "JWT" } - Payload(有效载荷): 用于携带实际数据,包括预设字段和自定义信息。例如:
{ "id": "1", "username": "Tom" } - Signature(签名): 通过对 Header 和 Payload 进行编码,并结合指定密钥及签名算法生成,用于验证令牌未被篡改,确保其安全性。
以下是一个完整的 JWT 示例,展示了经过 Base64 编码的头部与载荷,并通过密钥完成签名的结果。
签名的存在是保障 JWT 安全的核心。一旦令牌中的任何字符被修改,无论改动多么微小,在校验时都会导致验证失败。因此,JWT 具备极强的防篡改能力,保障了数据传输的安全性和可信度。
JWT 字符串的生成原理
JWT 如何将结构化的 JSON 数据转换为单一字符串?关键在于使用了 Base64 编码 技术。
Base64 是一种将二进制数据转化为 64 个可打印字符表示的编码方式,并非加密手段,因此是可以被解码还原的。这 64 个字符包括:A-Z、a-z、0-9、加号(+)、斜杠(/),以及用于填充的等号(=)。
所有原始数据在生成 JWT 时都会先进行 Base64 编码处理,从而形成可传输的字符串形式。需要注意的是,虽然 Base64 可以隐藏数据内容,但由于其可逆特性,不应将其视为加密保护措施。
JWT 的生成与验证流程
若要在项目中使用 JWT,需首先引入相关依赖库:
<!-- JWT依赖 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
2.1 生成 JWT 令牌
可通过在线工具生成符合 Base64 规范的密钥,用于签名过程。
接下来编写测试代码来创建一个 JWT 令牌。密钥也可以直接使用普通字符串。
// 生成 JWT 令牌
@Test
public void testGenerateJwt() {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id", 1);
dataMap.put("username", "admin");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, "b2po55yf5biF") // 指定签名算法与密钥
.addClaims(dataMap) // 添加自定义声明
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 设置过期时间(1小时)
.compact(); // 构建并生成令牌
System.out.println(jwt);
}
生成后的令牌可在 JWT 官网调试工具中进行解析与验证。
2.2 验证 JWT 令牌
为了验证接收到的 JWT 是否合法,需要对其进行解析并检查签名有效性。以下是测试代码示例:
// 解析并验证 JWT 令牌
@Test
public void testParseJWT() {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImV4cCI6MTc2NTA4NzY4NX0.vxiKt7Ld-9wxPj2jfvPFZ_tCyvpWU7HPbkhURp_0ThQ";
Claims claims = Jwts.parser()
.setSigningKey("b2po55yf5biF") // 提供相同的密钥
.parseClaimsJws(token) // 解析并验证签名
.getBody(); // 获取负载内容
System.out.println(claims);
}
JWT的编码与解码过程解析:
JWT编码流程
编码JWT的过程是将头部信息和有效载荷转换为一种紧凑且适用于URL的安全格式。首先,头部(用于声明签名算法及令牌类型)和有效载荷(包含如主题、过期时间、签发时间等声明内容)会被序列化为JSON格式,并通过Base64URL进行编码。随后,这两个编码后的部分使用英文句点“.”连接起来。接着,依据头部中指定的签名算法,结合密钥或私钥生成签名部分,该签名同样会经过Base64URL编码处理。最终,三部分(头部、有效载荷、签名)以“.”分隔组合成完整的JWT字符串,形成可用于传输或存储的标准格式。
JWT解码流程
解码操作则是对上述编码过程的逆向处理。它首先将JWT中Base64URL编码的头部和有效载荷还原为原始的JSON数据,使得任何人都可以在无需密钥的情况下查看这些公开信息。但通常所说的“解码”不仅限于数据解析,还包括对令牌完整性的验证。这一步骤要求使用与生成时相同的算法和密钥,对已解码的头部和有效载荷重新计算签名,并将结果与JWT中附带的签名部分进行比对。若两者一致,则说明令牌未被篡改,具备真实性和完整性。
三、实际应用示例
在项目开发中,可将JWT的生成与解析逻辑封装为一个工具类,提升代码复用性与维护效率。
定义LoginInfo实体类,用于封装用户登录后的返回信息,包括用户基本信息及生成的令牌。
在Service层实现用户登录功能:
// 登录方法
@Override
public LoginInfo login(Emp emp) {
Emp e = empMapper.selectByUsernameAndPassword(emp);
// 用户存在
if (e != null) {
LoginInfo info = new LoginInfo();
info.setId(e.getId());
info.setUsername(e.getUsername());
info.setName(e.getName());
// 构建JWT携带的自定义声明
Map<String, Object> claims = new HashMap<>();
claims.put("id", e.getId());
claims.put("username", e.getUsername());
// 生成JWT令牌
String jwt = JwtUtils.generateJwt(claims);
info.setToken(jwt);
return info;
}
// 用户不存在
return null;
}
测试结果展示:
如何实现对所有请求的统一认证校验?即在每次请求到达业务逻辑前自动检查JWT的有效性。常见的技术方案有两种:Filter(过滤器)和Interceptor(拦截器),可根据具体框架和技术栈选择合适的方式进行集成。


雷达卡


京公网安备 11010802022788号







