yashashvini15/Secure-Login-JWT-Authentication-Office-Task
GitHub: yashashvini15/Secure-Login-JWT-Authentication-Office-Task
基于 Spring Boot 的安全登录模块,实现 JWT 认证、Refresh Token 轮换、RBAC 权限控制及 OWASP 安全加固,并附带 16 项常见认证漏洞的分析与修复文档。
Stars: 0 | Forks: 0
# 安全登录模块 — JWT, Refresh Token, RBAC
一个基于 Spring Boot 的认证与授权模块,实现了基于 JWT 的无状态认证、Refresh Token 轮换、基于角色的访问控制(RBAC)、输入验证,以及符合 OWASP 标准的安全实践。
## 📌 作业目标
设计并实现一个安全的登录模块,并识别典型认证实现中的安全漏洞,涵盖:
- 基于 JWT (JSON Web Token) 的认证
- Refresh Token 机制
- 基于角色的访问控制 (RBAC)
- 密码哈希与输入验证
- OWASP 安全基础
## 🛠️ 技术栈
| 组件 | 技术 |
|---|---|
| 语言 | Java 17 |
| 框架 | Spring Boot |
| 安全 | Spring Security |
| 数据库 | MySQL (通过 Spring Data JPA / Hibernate) |
| JWT 库 | JWT (io.jsonwebtoken) 0.12.6 |
| 密码哈希 | BCrypt |
| 构建工具 | Maven |
## 🏗️ 架构概述
```
Client (Postman / Frontend)
│
▼
[POST /api/auth/signup] → Validate input → Hash password → Save user
│
▼
[POST /api/auth/login] → Verify password → Issue Access Token (JWT) + Refresh Token (Cookie)
│
▼
[Protected APIs] → JwtAuthFilter verifies token → Role checked (RBAC) → Response
│
▼
[POST /api/auth/refresh-token] → Rotate Refresh Token → New Access Token
│
▼
[POST /api/auth/logout] → Revoke Refresh Token
```
### 请求流程 (受保护的路由)
```
Request → JwtAuthFilter (validates JWT, sets SecurityContext)
→ SecurityConfig (checks role/authorization rules)
→ Controller (business logic)
→ Response
```
## 🗄️ 数据库设计
**`users` 表**
| 列名 | 类型 | 备注 |
|---|---|---|
| id | BIGINT (PK) | 自增 |
| name | VARCHAR(50) | 非空 |
| email | VARCHAR | 唯一,非空 |
| password_hash | VARCHAR | BCrypt 哈希 — 绝不使用明文 |
| role | VARCHAR | `USER` 或 `ADMIN` |
| created_at | TIMESTAMP | |
**`refresh_tokens` 表**
| 列名 | 类型 | 备注 |
|---|---|---|
| id | BIGINT (PK) | 自增 |
| user_id | BIGINT | 外键引用 |
| token | VARCHAR(500) | 唯一 |
| expiry_date | TIMESTAMP | |
| revoked | BOOLEAN | 用于轮换/重用检测 |
| device_info | VARCHAR | 可选 |
## 🔌 API Endpoints
| Endpoint | 方法 | 访问权限 | 描述 |
|---|---|---|---|
| `/api/auth/signup` | POST | 公开 | 注册新用户(默认角色:`USER`) |
| `/api/auth/login` | POST | 公开 | 认证用户,签发 Access + Refresh Token |
| `/api/auth/refresh-token` | POST | 公开 (基于 Cookie) | 轮换 token,签发新的 Access Token |
| `/api/auth/logout` | POST | 已认证 | 吊销 Refresh Token |
| `/api/user/profile` | GET | 任何已认证用户 | 获取个人资料(安全的 DTO,无密码) |
| `/api/admin/users` | GET | 仅限 `ADMIN` | 列出所有用户 — 强制执行 RBAC |
## 🔐 已实现的安全特性
### 1. 密码哈希
- 使用 BCrypt (`BCryptPasswordEncoder`) — 密码绝不以明文形式存储。
- Salting 由 BCrypt 自动处理。
### 2. JWT 认证
- 通过签名的 JWT (HMAC SHA-512) 进行无状态认证。
- Claims 包含: `sub` (email), `role`, `iat`, `exp`。
- 较短的有效期(15分钟),以在 token 泄露时限制暴露风险。
- Payload **不包含敏感数据**(无密码,无 PII)。
### 3. 带轮换的 Refresh Token
- Refresh Token 存储在服务器端(数据库),有效期较长(7天)。
- 存储在 `httpOnly`, `Secure`, `SameSite=Strict` cookie 中 — JavaScript 无法读取(防范 XSS)。
- **轮换**:每次刷新都会签发新 token 并使旧 token 失效。
- **重用检测**:如果重放已吊销/已使用的 token,该用户的所有会话都将被吊销(表明 token 被盗用)。
### 4. RBAC (基于角色的访问控制)
- 角色(`USER`, `ADMIN`)作为 JWT claim 嵌入。
- 在两个层面上强制执行:
- URL 层面: `SecurityConfig` → `.requestMatchers("/api/admin/**").hasRole("ADMIN")`
- 方法层面: 在 controller 方法上使用 `@PreAuthorize("hasRole('ADMIN')")`。
- 角色**始终在服务器端设置**(注册时硬编码为 `"USER"`) — 绝不接受来自客户端的输入,防止权限提升。
### 5. 输入验证
- Bean Validation (JSR-380) 注解: `@NotBlank`, `@Email`, `@Size`, `@Pattern`。
- 强制要求密码复杂度(大写字母、小写字母、数字、特殊字符,至少 8 个字符)。
- 验证在服务器端进行 — 绝不仅依赖客户端。
### 6. 通用错误消息
- 登录失败返回通用的 `"Invalid credentials"` 消息(而不是“邮箱不存在”或“密码错误”),以防止用户枚举攻击。
### 7. 集中式异常处理
- `GlobalExceptionHandler` (`@RestControllerAdvice`) 确保不会向客户端暴露任何堆栈跟踪或内部细节。
### 8. 使用 DTO 代替实体
- API 响应使用 DTO(`UserProfileResponse`, `LoginResponse`) — `User` 实体(包含 `passwordHash`)绝不会在响应中被直接序列化。
## 🚨 识别出的安全漏洞(常见错误)及已应用的修复
| # | 安全漏洞 | 风险 | 应用的修复 |
|---|---|---|---|
| 1 | 以明文或使用 MD5/SHA1 存储密码 | 发生泄露时所有凭证外泄 | BCrypt 哈希(缓慢、加盐) |
| 2 | 明确的登录错误消息(区分邮箱或密码错误) | 用户/邮箱枚举攻击 | 通用的 `"Invalid credentials"` 消息 |
| 3 | JWT payload 中包含敏感数据 | Payload 是编码而非加密的 — 任何人都可以读取 | 仅存储非敏感 claims (email, role) |
| 4 | 长期有效的 Access Token | 如果 token 被盗,攻击窗口极大 | 15 分钟有效期 + Refresh Token 模式 |
| 5 | Refresh Token 存储在 `localStorage` 中 | 容易受到 XSS 攻击而被盗 | `httpOnly` + `Secure` + `SameSite=Strict` cookie |
| 6 | `/login` 没有速率限制 | 暴力破解密码 | *(记录为推荐的增强功能 — 见未来改进)* |
| 7 | 原始 SQL 字符串拼接 | SQL 注入 | Spring Data JPA (自动使用预编译语句) |
| 8 | 在 API 响应中返回完整的 `User` 实体 | 在响应体中泄露 `password_hash` | 所有响应均使用专用的 DTO |
| 9 | 仅在前端进行角色检查(隐藏 UI 按钮) | 可通过 Postman/curl 直接访问后端绕过 | 通过 `@PreAuthorize` + URL 匹配器进行后端强制验证 |
| 10 | 资源访问时没有归属权检查 (IDOR) | 用户 A 可以通过更改 ID 访问用户 B 的数据 | 在返回数据前在服务器端验证归属权 |
| 11 | 错误响应中返回详细的堆栈跟踪 | 向攻击者暴露内部结构 | `GlobalExceptionHandler` 仅返回通用消息 |
| 12 | 没有 HTTPS | 中间人攻击,token/密码被拦截 | 强制执行 `Secure` cookie 标志(生产环境中必须使用 HTTPS) |
| 13 | 没有 Refresh Token 重用检测 | 被盗 token 可在有效期内被静默使用 | Token 轮换 + 重用检测(自动吊销所有会话) |
| 14 | 在 JWT 验证期间盲目信任 `alg` 头 | `alg:none` 攻击 — 伪造的 token 被接受 | 通过签名密钥类型显式强制指定算法 |
| 15 | 在注册期间接受来自客户端的 `role` | 任何人都可以自行注册为 `ADMIN` | 角色在服务器端硬编码为 `"USER"`;绝不从请求体中读取 |
| 16 | 自助“更改我的角色” endpoint | 权限提升 — 任何用户都可以提升自己的权限 | 不存在这样的 endpoint;角色更改需要现有的 `ADMIN`(或数据库层面的引导) |
## 🧪 测试证据(手动,通过 Postman)
| 测试用例 | 预期结果 | 状态 |
|---|---|---|
| 使用有效数据注册 | `200 OK` | ✅ 通过 |
| 使用正确的凭证登录 | `200 OK` + Access Token + Refresh Token cookie | ✅ 通过 |
| 使用有效 token 访问 `/api/user/profile` | `200 OK` + 个人资料数据 | ✅ 通过 |
| 以 `USER` 角色访问 `/api/admin/users` | `403 Forbidden` | ✅ 通过 |
| 以 `ADMIN` 角色访问 `/api/admin/users`(更新角色并重新登录后) | `200 OK` + 用户列表 | ✅ 通过 |
| 使用过期的 token 访问 `/api/user/profile` | `403 Forbidden`(平滑拒绝,无崩溃) | ✅ 通过 |
| Refresh Token endpoint | `200 OK` + 新的 Access Token + 轮换后的 cookie | ✅ 通过 |
| 登出 | Refresh Token 从数据库中吊销 | ✅ 通过 |
**关键观察:** 在角色更改(`USER`)之前签发的旧 JWT 将以*旧*角色保持有效,直到它过期 — 用户必须重新登录才能获得反映更新后 `ADMIN` 角色的 token。这演示了无状态 JWT 著名的**陈旧 claims 权衡 (stale-claims tradeoff)**。
## 🔮 未来改进(未实现 — 为完整性而记录)
- 在 `/login` 和 `/signup` 上进行**速率限制**,以防止暴力破解攻击。
- 多次登录失败后的**账户锁定**。
- 如果系统演变为微服务架构,使用 **RS256 (非对称签名)** 代替 HS256。
- **Token 黑名单 (Redis)**,用于在登出/安全事件发生时立即撤销 Access Token。
- 针对管理员操作和失败登录尝试的**审计日志**。
- 在注册/登录时使用 **CAPTCHA** 来防范机器人。
- 将 JWT 密钥移至环境变量或 secrets manager 中,而不是 `application.properties`。
## ⚙️ 设置说明
1. 克隆代码库。
2. 创建一个 MySQL 数据库:
CREATE DATABASE secure_login_db;
3. 使用您的 MySQL 凭据更新 `src/main/resources/application.properties`。
4. 运行应用程序:
mvn spring-boot:run
5. 使用 Postman 测试 endpoints(上面列出的集合 endpoints)。
## 📚 展示的关键概念
- 认证与授权
- 密码哈希 (BCrypt) 与加密
- JWT 结构(Header, Payload, Signature)及其无状态特性
- Access Token 与 Refresh Token 的权衡
- Refresh Token 轮换与重用检测
- 多层 RBAC 强制执行
- OWASP Top 10 相关风险:SQL 注入、XSS、CSRF、失效的认证、失效的访问控制、安全配置错误、敏感数据泄露
标签:JWT, RBAC, Spring Boot, Syscall, Web开发, 域名枚举