Trae-Maven/jwt-security
GitHub: Trae-Maven/jwt-security
一个面向现代 Java Spring 应用的轻量级 JWT 安全库,提供 Ed25519 签名、令牌轮换和多层防护机制。
Stars: 0 | Forks: 0
# Jwt 安全
一个轻量级且灵活的 Java JWT 认证与安全库。
Jwt Security 提供了简单的工具和辅助程序,用于在 Java 应用程序中生成、验证和管理 JSON Web Token。
该库设计轻量、快速,并且易于集成到现有的基于 Spring 的 Java 应用程序中。
专为现代 Java (Java 21+) 构建,旨在与现有的 Spring 基础设施无缝集成。
## 功能特性
- Ed25519 (EdDSA) 非对称签名 —— 无共享密钥,符合 FAPI 2.0 标准,通过 TLS 1.3 批准
- 为访问令牌和刷新令牌使用独立的密钥对(密钥隔离)
- 可配置的令牌生命周期 —— 通过设置提供程序设置访问和刷新过期时间
- 确定性或临时的密钥对 —— 从主密钥派生以支持多实例,或在启动时生成全新的密钥对
- 令牌指纹绑定 —— 防止通过 XSS 窃取令牌
- 带有重用检测的刷新令牌轮换 —— 重放的令牌会触发完整的账户撤销
- 并发轮换宽限期 —— 防止来自并行浏览器请求的误报重用检测
- 恒定时间哈希比较,以防止时序侧信道攻击
- `lastTokenIssueAt` 验证 —— 无需黑名单即可实现即时全局令牌失效(仅限安全事件)
- 生产环境中使用 `__Host-` Cookie 前缀 —— 浏览器强制执行 Secure + Path=/ + no Domain
- JWT 令牌生成和验证
- 令牌解析和声明访问
- 轻量级安全工具
- 最小化的依赖项
- 专为现代 Java (Java 21+) 设计
- 易于集成到 Spring 应用程序
## 环境要求
Jwt Security 专为基于 Spring 的 Web 应用程序设计。
您的项目必须已包含以下依赖项(这些通常已包含在大多数 Spring Boot 应用程序中):
```
org.projectlombok
lombok
1.18.36
provided
org.springframework
spring-context
7.0.3
org.springframework
spring-web
7.0.3
jakarta.servlet
jakarta.servlet-api
6.0.0
org.bouncycastle
bcprov-jdk18on
1.83
```
这些依赖项在 Jwt Security 中被标记为 **provided**,因为预期它们已存在于您的应用程序中。
## 内置依赖
Jwt Security 包含若干依赖项,在您安装该库时会自动包含。
- [Utilities](https://github.com/Trae-Maven/utilities) – 框架内部使用的共享辅助类和专注于性能的工具。
```
io.jsonwebtoken
jjwt-api
0.12.5
io.jsonwebtoken
jjwt-impl
0.12.5
io.jsonwebtoken
jjwt-jackson
0.12.5
```
这些依赖项在安装 Jwt Security 时会自动包含,无需手动添加。
## 安装
将依赖项添加到您的 Maven 项目中:
```
io.github.trae
jwt-security
0.0.2
```
## 集成指南
Jwt Security 需要在您的应用程序中设置五个类。每个提供程序都是一个接口,由您根据自己的应用程序逻辑实现,而具体的 `JwtService` 子类消除了每个注入点冗长的泛型。
### 1. 定义您的角色枚举
创建一个实现 `JwtAccountRoleProvider` 的角色枚举:
```
public enum Role implements JwtAccountRoleProvider {
ADMINISTRATOR, MODERATOR, STANDARD
}
```
### 2. 实现您的账户实体
您的账户类必须使用您的角色枚举实现 `JwtAccountProvider`:
```
@AllArgsConstructor
@Getter
@Setter
public class Account implements JwtAccountProvider {
private UUID id;
private Role role;
private long lastTokenIssueAt;
private RefreshToken refreshToken;
@Override
public boolean hasRole(final Role role) { return this.getRole().ordinal() >= role.ordinal(); }
}
```
### 3. 实现您的账户管理器
创建一个处理账户持久化的服务:
```
@AllArgsConstructor
@Service
public class AccountManager implements JwtAccountManagerProvider {
private final AccountRepository accountRepository;
@Override
public Optional getAccountById(final UUID id) {
return this.accountRepository.findById(id);
}
@Override
public void updateAccountLastTokenIssueAt(final Account account) {
this.accountRepository.updateLastTokenIssueAt(account.getId(), account.getLastTokenIssueAt());
}
@Override
public void updateAccountRefreshToken(final Account account) {
this.accountRepository.updateRefreshToken(account.getId(), account.getRefreshToken());
}
}
```
### 4. 实现您的设置
使用您的环境设置、令牌生命周期和密钥派生策略配置 JWT 服务。
**确定性密钥**(推荐用于多实例部署):
```
@Component
public class MyJwtSettings implements JwtSettingsProvider {
@Override
public boolean isProduction() { return true; }
@Override
public Duration getAccessTokenExpiration() { return Duration.ofMinutes(5); }
@Override
public Duration getRefreshTokenExpiration() { return Duration.ofDays(14); }
@Override
public String getIssuer() { return "myapp.com"; }
@Override
public byte[] getAccessTokenKeySeed() {
return KeyDerivation.derive("my-master-secret:access");
}
@Override
public byte[] getRefreshTokenKeySeed() {
return KeyDerivation.derive("my-master-secret:refresh");
}
}
```
**令牌生命周期建议:**
| 令牌 | 推荐 | 范围 | 说明 |
|---|---|---|---|
| Access | 5 分钟 | 1–15 分钟 | 时间越短 = 被盗令牌的风险越低。无服务器端撤销,因此生命周期是唯一的控制手段。 |
| Refresh | 14 天 | 7–30 天 | 时间越长 = 强制重新登录的次数越少。轮换和重用检测降低了风险。 |
**临时密钥**(每次重启都会使令牌失效):
```
@Override
public byte[] getAccessTokenKeySeed() { return null; }
@Override
public byte[] getRefreshTokenKeySeed() { return null; }
```
### 5. 创建您的 JwtService 子类
创建一个具体的子类,在一个位置绑定所有泛型类型。这避免了在应用程序的每个注入点重复 `JwtService`:
```
@Service
public class MyJwtService extends JwtService {
public MyJwtService(final MyJwtSettings settings, final AccountManager accountManager) {
super(settings, accountManager);
}
}
```
这是推荐的方法。您只需在这里定义一次泛型,然后在其他地方注入 `MyJwtService`,无需任何泛型噪音。
## 密钥派生
Jwt Security 支持两种 Ed25519 密钥对管理模式。
### 确定性密钥(推荐用于多实例)
当 `getAccessTokenKeySeed()` 和 `getRefreshTokenKeySeed()` 返回一个 32 字节的种子时,确定性 Ed25519 密钥对将使用 BouncyCastle 派生。每个具有相同主密钥的应用程序实例都会生成相同的密钥对 —— 无需共享密钥文件、挂载卷或密钥分发。
种子在密钥派生后立即从内存中擦除。
这是负载均衡器后面有多个实例的生产部署的推荐方法。
### 内置 KeyDerivation 工具(可选)
Jwt Security 附带了一个 `KeyDerivation` 工具类,该类使用带有 HMAC-SHA256 的 HKDF 从上下文字符串派生出确定性的 32 字节密钥。这会生成一个适合 Ed25519 密钥对派生的种子,可以直接与设置提供程序一起使用:
```
import io.github.trae.jwtsecurity.utility.KeyDerivation;
@Override
public byte[] getAccessTokenKeySeed() {
return KeyDerivation.derive("my-master-secret:access");
}
@Override
public byte[] getRefreshTokenKeySeed() {
return KeyDerivation.derive("my-master-secret:refresh");
}
```
这完全是可选的 —— 您可以使用任何密钥派生策略(HKDF、PBKDF2 等),只要种子方法在所有应用程序实例中为相同的输入返回一致的 32 字节数组即可。
### 临时密钥(默认)
当种子方法返回 `null` 时,将使用 JDK 内置的 Ed25519 提供程序在启动时生成新的密钥对。每次重启时,所有未完成的令牌都会失效。这是最安全的选项,适用于可以接受在部署时强制重新认证的应用程序。
## 用法
一旦您的 `MyJwtService` 注册完毕,您就可以在应用程序的任何地方注入它 —— 无需泛型:
```
@AllArgsConstructor
@Controller
public class AuthController {
private final MyJwtService jwtService;
@PostMapping("/login")
public String login(HttpServletRequest request, HttpServletResponse response) {
Account account = this.authenticate(request); // your authentication logic
this.jwtService.applyTokenCookies(request, response, account);
return "redirect:/dashboard";
}
@PostMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
Optional account = this.jwtService.getAccountByRequest(request, response);
this.jwtService.removeTokenCookies(response, account.orElse(null));
return "redirect:/login";
}
@GetMapping("/dashboard")
public String dashboard(HttpServletRequest request, HttpServletResponse response, Model model) {
Optional account = this.jwtService.getAccountByRequest(request, response);
if (account.isEmpty()) {
return "redirect:/login";
}
return "dashboard";
}
@GetMapping("/admin")
public String admin(HttpServletRequest request, HttpServletResponse response) {
if (!this.jwtService.isAuthenticatedByRole(request, response, Role.ADMINISTRATOR)) {
return "redirect:/login";
}
return "admin";
}
}
```
## 安全概览
| 层级 | 保护措施 |
|---|---|
| **签名算法** | Ed25519 (EdDSA) —— 非对称、确定性、抗侧信道攻击 |
| **密钥隔离** | 为访问令牌和刷新令牌使用独立的密钥对 |
| **密钥派生** | 从主密钥通过 HKDF 派生 → 确定性的 Ed25519 种子(多实例安全) |
| **令牌生命周期** | 通过设置提供程序配置 —— 推荐 5 分钟 Access / 14 天 Refresh |
| **令牌绑定** | JWT 中的指纹哈希 + HttpOnly Cookie 中的原始值 |
| **XSS 防御** | HttpOnly Cookie —— JavaScript 无法访问令牌值 |
| **CSRF 防御** | 生产环境中使用 SameSite=Strict —— 浏览器阻止跨源请求 |
| **Cookie 加固** | `__Host-` 前缀强制执行 Secure + Path=/ + no Domain |
| **令牌盗窃** | 指纹绑定使被盗的 JWT 如果没有 Cookie 则无法使用 |
| **重放防护** | 每次轮换时在服务器端验证刷新令牌 JTI 哈希 |
| **重用检测** | 刷新令牌哈希不匹配会触发完整的账户撤销(针对并发请求设有宽限期) |
| **并发安全** | 轮换宽限期防止来自并行浏览器请求的误报重用检测 |
| **会话失效** | lastTokenIssueAt —— 在安全事件(登录、密码更改、强制登出)时更新,以立即撤销所有令牌 |
| **时序攻击** | 所有验证检查均采用恒定时间哈希比较 |
| **内存安全** | 密钥种子在派生后立即从内存中擦除 |
标签:Cookie安全, Ed25519, EdDSA, FAPI2.0, Java21, JWT, LangChain, Modbus, Spring, SpringBoot, Streamlit, Token验证, Web安全, YAML, 云配置检测, 会话管理, 侧信道攻击防护, 刷新令牌, 单点登录, 域名枚举, 安全库, 开源库, 搜索引擎爬虫, 蓝队分析, 访问控制, 轻量级, 防XSS, 防重放攻击, 非对称加密