从"记住登录"到JWT:深入理解Web认证技术的演进与实践
一、HTTP协议的本质:无记忆的通信机制
每当用户访问一个网站时,背后起作用的是HTTP协议。该协议具有“无状态”特性——这意味着每一次请求都是独立事件,服务器不会自动保留任何关于之前交互的信息。
这就像去便利店购买饮料,每次交易都彼此无关,店员并不记得你上个月买过什么。同样地,在网页通信中,每个请求对服务器而言都是一次全新的对话。
https://
http://
二、如何在多次请求间识别用户身份
当用户完成登录操作后,系统必须能够在后续的多个请求中持续识别其身份。为此,诞生了会话跟踪技术,旨在解决HTTP无状态带来的身份识别难题。
Part1:基于Cookie的身份标识机制
工作原理
服务器通过响应头将数据写入用户的浏览器(如Set-Cookie),这些数据即为Cookie。之后,浏览器会在每次向同一域名发起请求时,自动携带Cookie信息(通过Cookie请求头)回传给服务器。
Set-Cookie
user_id=123
Cookie
典型应用场景
- 自动填充用户名
- 临时保存购物车内容
- 验证码状态维持
存在的局限性
- 用户可手动禁用Cookie,导致功能异常
- 部分移动端APP环境不支持Cookie机制
- 无法实现跨域共享:不同协议、IP/域名或端口之间无法共用Cookie
例如,从https://a.example.com登录后跳转至https://b.example.com,由于属于不同子域,原有状态将丢失。
192.168.150.100:8080
192.168.150.20:90
判断是否跨域主要依据三个维度:
- 协议(如http与https)
- IP地址或域名
- 端口号
Part2:Session机制——服务端维护会话数据
运行机制
用户成功登录后,服务器创建唯一的会话ID(Session ID),并通过Cookie发送给客户端。此后每次请求都会附带此ID,服务器根据ID查找存储在内存或数据库中的完整会话信息(如用户权限、登录时间等)。
SESS_abc123
典型问题场景
假设你在公司使用电脑登录内网系统,此时Session存储在服务器A上;第二天换到学校机房访问相同系统,请求被负载均衡分配至服务器B。由于各服务器未共享Session数据,系统无法识别你的身份,从而要求重新登录。
注意
这种现象常见于集群部署环境下,因Session未能同步而导致重复登录的问题。
Session
图示说明:尽管每次请求均包含Cookie,但由于跨域限制及服务器间数据不同步,仍需反复进行身份验证。
Part3:JWT令牌——现代主流认证方案
核心优势
- 无状态设计:无需服务器保存会话信息
- 天然支持跨域:适用于前后端分离架构
- 适应集群部署:各节点可独立验证令牌
- 多平台兼容:适用于PC网页、移动App等多种终端
JWT结构解析
JWT(JSON Web Token)是一个自包含的字符串令牌,由三部分组成,以点号分隔:
- Header(头部):声明令牌类型和签名算法(如HS256),经Base64Url编码
- Payload(负载):存放用户信息、过期时间等自定义声明
- Signature(签名):使用密钥对前两部分加密生成,确保令牌不可篡改
为何选择JWT?
- 支持分布式部署:无需在多个服务器间同步Session数据,每个节点均可独立校验令牌有效性
- 良好的跨域能力:适合前端(如Vue/React)与后端分离开发模式
- 降低服务端存储压力:所有必要信息已嵌入令牌本身,无需频繁查询数据库
- 全平台通用:无论是Web、iOS还是Android应用,均可统一采用JWT进行认证
注:虽然JWT具备诸多优点,但也需要开发者自行实现令牌刷新、黑名单管理等功能,属于“手动化”程度较高的方案。
Vue
SpringBoot
实战案例:Tlias项目中的JWT集成
一、环境搭建
若使用Maven构建项目,请在项目的pom.xml文件中添加以下依赖项:
pom.xml
jjwt
<dependencies>
<!-- JJWT API -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- JJWT 实现 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- JJWT Jackson 支持(用于 JSON 处理) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
二、编写JWT工具类
代码详解
1. 密钥生成
为保证安全性,需使用强随机密钥进行签名。推荐使用HS256算法配合至少256位长度的密钥。
2. 令牌有效期设置
合理设定过期时间(如30分钟),避免长期有效带来的安全风险。可通过exp声明控制失效时间。
3. 生成令牌
将用户基本信息(如ID、角色)写入Payload,并结合密钥生成完整的JWT字符串返回给客户端。
4. 解析令牌
接收客户端传来的JWT,验证签名有效性并提取其中的Payload数据,用于权限判断和身份识别。
完整代码示例
(此处省略具体Java实现代码,实际开发中应封装成工具类提供统一接口)
收获总结
通过本篇内容的学习,我们了解了从传统Cookie/Session到现代JWT的认证技术演进路径:
- Cookie解决了基础的数据存储问题,但受限于浏览器策略和跨域障碍
- Session增强了安全性,却带来了服务器状态依赖和扩展难题
- JWT以其无状态、易扩展、跨平台等优势,成为当前企业级系统的首选方案
结语
尽管目前纯Cookie/Session方式在实际生产环境中已逐渐被替代,但在高校教学和考试中仍常作为基础知识讲解。对于希望掌握前沿技术的开发者来说,直接聚焦JWT及相关生态是更高效的学习路径。
掌握JWT不仅意味着学会一种认证手段,更是理解现代分布式系统设计理念的重要一步。
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
请依据最新版本进行配置调整。
jjwt
开发环境准备
确保已正确配置 JDK 开发环境,以支持后续的 JWT 功能实现。
二、JWT 工具类设计与实现
我们将构建一个工具类用于生成和解析 JWT 令牌。以下为具体代码结构及详细说明。
JwtUtils
1. 密钥生成机制
采用如下方式初始化签名密钥:
private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
通过 Keys.secretKeyFor 方法自动生成符合 HS256 算法安全标准的密钥。
注意:在生产环境中,应避免将密钥直接硬编码在源码中,推荐使用环境变量或专用密钥管理服务进行安全管理。
Keys.secretKeyFor
2. 设置令牌有效期
定义常量控制令牌有效时长:
private static final long EXPIRATION_TIME = 24 * 60 * 60 * 1000L; // 24小时
可根据实际业务需求灵活调整该时间值。
3. 生成 JWT 令牌
核心方法签名如下:
public static String generateToken(Map<String, Object> claims)
功能描述:基于传入的键值对数据(如用户ID、用户名等)生成加密的 JWT 字符串。
执行流程:
- 利用
Jwts.builder() 初始化一个 JWT 构建器实例;
- 调用
.addClaims(claims) 添加自定义声明内容至 Payload 区域;
- 通过
.signWith(SECRET_KEY, SignatureAlgorithm.HS256) 指定签名算法与密钥,保障令牌完整性;
- 设置过期时间戳:
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME));
- 最终调用
.compact() 方法完成 JWT 的序列化输出。
Jwts.builder()
addClaims(claims)
signWith(SECRET_KEY, SignatureAlgorithm.HS256)
setExpiration(...)
compact()
4. 解析并验证 JWT 令牌
对应解析方法定义如下:
public static Claims parseToken(String token) throws Exception
功能说明:对接收到的 JWT 字符串进行解码与校验,返回包含原始声明信息的 Claims 对象。
处理步骤:
- 使用
Jwts.parserBuilder() 创建解析器构造器;
- 通过
.setSigningKey(SECRET_KEY) 配置用于验证签名的密钥;
- 调用
.build() 完成解析器实例构建;
- 执行
.parseClaimsJws(token) 对令牌进行完整验证(包括格式、签名、时效性等);
- 若验证成功,则提取其中的载荷部分,并通过
.getBody() 获取封装了所有自定义信息的 Claims 实例。
一旦令牌无效、过期或签名不匹配,系统将抛出相应异常,需由上层逻辑捕获处理。
Claims
Jwts.parserBuilder()
setSigningKey(SECRET_KEY)
build()
parseClaimsJws(token)
getBody()
Claims
完整工具类代码
package com.example.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtils {
// 使用 HS256 算法生成的安全密钥
// 注意:生产环境下请勿硬编码密钥,建议从外部配置加载
private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 设定令牌有效时间为 24 小时(毫秒)
private static final long EXPIRATION_TIME = 24 * 60 * 60 * 1000L; // 24小时
/**
* 生成 JWT 令牌
*
* @param claims 包含用户相关信息的键值对集合
* @return 返回生成的 JWT 字符串
*/
public static String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.addClaims(claims)
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.compact();
}
/**
* 解析并验证 JWT 令牌
*
* @param token 待解析的 JWT 字符串
* @return 解析成功的声明对象
* @throws Exception 若令牌无效、过期或签名错误则抛出异常
*/
public static Claims parseToken(String token) throws Exception {
return Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody();
}
}
// 设置令牌过期时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
// 生成并返回紧凑格式的 JWT 字符串
.compact();
}
/**
* 解析 JWT 令牌
*
* @param token 需要解析的 JWT 令牌字符串
* @return 包含令牌数据的 Claims 对象
* @throws Exception 当令牌无效、已过期或签名验证失败时抛出异常
*/
public static Claims parseToken(String token) throws Exception {
return Jwts.parserBuilder()
// 指定签名所用的密钥
.setSigningKey(SECRET_KEY)
// 创建 JWT 解析器实例
.build()
// 解析传入的令牌并进行校验
.parseClaimsJws(token)
// 提取 Payload 中的声明信息(Claims)
.getBody();
}
收获总结
通过本文的学习,已经基本掌握了三种持续演进的 Web 认证方式,深入理解了当前较为流行的 JWT 技术。该技术具备良好的无状态性与扩展性,适用于现代分布式系统的身份验证场景,可直接应用于实际开发项目中。
结语
本文由初学者编写,内容可能存在疏漏或错误之处,恳请各位经验丰富的朋友不吝指正,以便持续改进和完善。