# 登录注册加密完整说明文档 ## 📋 目录 1. [数据库表结构](#数据库表结构) 2. [密码加密机制](#密码加密机制) 3. [用户注册流程](#用户注册流程) 4. [用户登录流程](#用户登录流程) 5. [JWT Token机制](#jwt-token机制) 6. [代码文件位置汇总](#代码文件位置汇总) --- ## 数据库表结构 ### 用户表 (user) **表名:** `user` **字段说明:** | 字段名 | 类型 | 说明 | 备注 | |--------|------|------|------| | `user_id` | INT | 用户ID | 主键,自增 | | `username` | VARCHAR | 用户名 | 唯一,用于登录 | | `password` | VARCHAR | 密码 | **BCrypt加密存储**,格式:`$2a$10$...` | | `nickname` | VARCHAR | 昵称 | 显示名称 | | `avatar_url` | VARCHAR | 头像URL | 用户头像地址 | | `phone` | VARCHAR | 手机号 | 可选 | | `email` | VARCHAR | 邮箱 | 可选 | | `user_role` | INT | 用户角色 | 0:普通用户,1:管理员,2:超级管理员 | | `member_level` | INT | 会员等级 | 0:普通,1:VIP | | `create_time` | DATETIME | 创建时间 | 注册时间 | | `last_login_time` | DATETIME | 最后登录时间 | 登录时更新 | | `status` | INT | 状态 | 1:正常,0:禁用 | | `face_person_id` | VARCHAR | 人脸识别ID | 百度人脸识别 | | `face_bind_status` | INT | 人脸绑定状态 | 0:未绑定,1:已绑定 | | `last_face_verify_time` | DATETIME | 最后人脸验证时间 | 管理员人脸验证 | **实体类位置:** `book/src/main/java/com/zhentao/pojo/User.java` --- ## 密码加密机制 ### 1. 加密算法:BCrypt **为什么使用BCrypt?** - ✅ **单向哈希**:无法逆向解密,只能验证 - ✅ **自动加盐**:每次加密结果不同,但都能验证通过 - ✅ **计算成本可调**:默认强度为10,可防止暴力破解 - ✅ **行业标准**:Spring Security推荐使用 ### 2. 加密配置 **配置文件位置:** `book/src/main/java/com/zhentao/config/SecurityConfig.java` ```java @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } ``` **关键代码:** 第16-18行 ### 3. 密码加密格式 加密后的密码格式:`$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy` - `$2a$` - BCrypt版本标识 - `10` - 加密强度(cost factor) - 后面60个字符 - 盐值和哈希值 --- ## 用户注册流程 ### 完整流程图 ``` 用户提交注册信息 ↓ Controller接收请求 (UserAuthController.register) ↓ 检查用户名是否已存在 ↓ 使用BCrypt加密密码 (UserServiceImpl.register) ↓ 设置默认值(角色、会员等级、状态) ↓ 保存到数据库 (user表) ↓ 返回注册结果 ``` ### 代码位置详解 #### 1. 注册接口(Controller层) **文件位置:** `book/src/main/java/com/zhentao/controller/UserAuthController.java` **方法:** `register()` - 第45-52行 ```java @PostMapping("/register") public Result register(@RequestBody User user) { user.setUserRole(0); // 普通用户注册 if (userService.register(user)) { return Result.success("注册成功"); } return Result.error("用户名已存在"); } ``` **接口地址:** `POST /api/user/register` **请求示例:** ```json { "username": "testuser", "password": "123456", "nickname": "测试用户", "phone": "13800000000", "email": "test@example.com" } ``` #### 2. 注册业务逻辑(Service层) **文件位置:** `book/src/main/java/com/zhentao/service/impl/UserServiceImpl.java` **方法:** `register()` - 第47-67行 ```java @Override public boolean register(User user) { // 1. 检查用户名是否已存在 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getUsername, user.getUsername()); if (count(wrapper) > 0) { return false; // 用户名已存在 } // 2. 【关键】使用BCrypt加密密码 user.setPassword(passwordEncoder.encode(user.getPassword())); // 3. 设置默认值 if (user.getUserRole() == null) { user.setUserRole(0); // 默认普通用户 } if (user.getMemberLevel() == null) { user.setMemberLevel(0); // 默认普通会员 } if (user.getStatus() == null) { user.setStatus(1); // 默认正常状态 } // 4. 保存到数据库 return save(user); } ``` **关键代码:** 第53行 - `user.setPassword(passwordEncoder.encode(user.getPassword()));` #### 3. Service接口定义 **文件位置:** `book/src/main/java/com/zhentao/service/UserService.java` **方法:** `register(User user)` - 第9行 #### 4. 数据访问层(Mapper) **文件位置:** `book/src/main/java/com/zhentao/mapper/UserMapper.java` 使用MyBatis-Plus,无需手写SQL,自动实现CRUD操作。 --- ## 用户登录流程 ### 完整流程图 ``` 用户提交登录信息 ↓ Controller接收请求 (UserAuthController.login) ↓ Service验证用户名和密码 (UserServiceImpl.login) ↓ 检查密码格式(BCrypt或明文) ↓ 使用BCrypt验证密码 ↓ 检查用户状态和角色 ↓ 生成JWT Token (JwtUtil.generateToken) ↓ 返回Token和用户信息 ``` ### 代码位置详解 #### 1. 普通用户登录接口 **文件位置:** `book/src/main/java/com/zhentao/controller/UserAuthController.java` **方法:** `login()` - 第22-43行 ```java @PostMapping("/login") public Result> login(@RequestBody Map params) { String username = params.get("username"); String password = params.get("password"); // 1. 验证用户名和密码 User user = userService.login(username, password); if (user == null) { return Result.error("用户名或密码错误"); } // 2. 禁止管理员通过小程序端登录 if (user.getUserRole() != null && user.getUserRole() == 1) { return Result.error(403, "管理员账号请使用Web端管理后台登录"); } // 3. 检查账号状态 if (user.getStatus() != null && user.getStatus() != 1) { return Result.error("账号已被禁用"); } // 4. 生成JWT Token String token = jwtUtil.generateToken(user.getUserId(), user.getUsername(), user.getUserRole()); // 5. 返回结果 Map data = new HashMap<>(); data.put("token", token); data.put("user", user); return Result.success(data); } ``` **接口地址:** `POST /api/user/login` **请求示例:** ```json { "username": "testuser", "password": "123456" } ``` #### 2. 管理员登录接口 **文件位置:** `book/src/main/java/com/zhentao/controller/AdminController.java` **方法:** `login()` - 第20-39行 ```java @PostMapping("/login") public Result> login(@RequestBody Map params, HttpServletRequest request) { String username = params.get("username"); String password = params.get("password"); User user = userService.login(username, password); if (user == null) { return Result.error("用户名或密码错误"); } // 只允许管理员登录 if (user.getUserRole() != 1 && user.getUserRole() != 2) { return Result.error("无管理员权限"); } if (user.getStatus() != 1) { return Result.error("账号已被禁用"); } // 使用Session存储(Web端) AdminSessionUtil.setLoginUser(request, user); Map data = new HashMap<>(1); data.put("user", user); return Result.success(data); } ``` **接口地址:** `POST /api/admin/login` #### 3. 登录验证逻辑(Service层) **文件位置:** `book/src/main/java/com/zhentao/service/impl/UserServiceImpl.java` **方法:** `login()` - 第20-44行 ```java @Override public User login(String username, String password) { // 1. 根据用户名查询用户 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getUsername, username); User user = getOne(wrapper); if (user == null) { return null; // 用户不存在 } // 2. 检查密码格式 String storedPassword = user.getPassword(); if (storedPassword != null && storedPassword.startsWith("$2")) { // BCrypt格式,使用matches验证 if (passwordEncoder.matches(password, storedPassword)) { return user; // 密码正确 } } else { // 明文密码(兼容旧数据),直接比较 if (password.equals(storedPassword)) { // 自动升级为BCrypt加密 user.setPassword(passwordEncoder.encode(password)); updateById(user); return user; } } return null; // 密码错误 } ``` **关键代码:** - 第29行:检查是否为BCrypt格式 - 第31行:使用 `passwordEncoder.matches()` 验证密码 - 第38行:自动升级明文密码为BCrypt加密 --- ## JWT Token机制 ### 1. JWT工具类 **文件位置:** `book/src/main/java/com/zhentao/common/JwtUtil.java` **主要方法:** | 方法 | 说明 | 行号 | |------|------|------| | `generateToken()` | 生成JWT Token | 第46-52行 | | `getClaimsFromToken()` | 解析Token获取Claims | 第65-71行 | | `getUserIdFromToken()` | 从Token中获取用户ID | 第73-76行 | | `validateToken()` | 验证Token是否有效 | 第78-85行 | **配置位置:** `book/src/main/resources/application.yml` ```yaml jwt: secret: zhentao-book-secret-key-2024-this-is-a-very-long-secret-key-for-jwt-encryption-which-must-be-at-least-64-characters-long expiration: 86400000 # 24小时(毫秒) ``` ### 2. Token生成流程 **代码位置:** `book/src/main/java/com/zhentao/common/JwtUtil.java` 第46-52行 ```java public String generateToken(Integer userId, String username, Integer userRole) { Map claims = new HashMap<>(); claims.put("userId", userId); claims.put("username", username); claims.put("userRole", userRole); return createToken(claims); } ``` **Token包含的信息:** - `userId` - 用户ID - `username` - 用户名 - `userRole` - 用户角色 - `iat` - 签发时间(自动添加) - `exp` - 过期时间(自动添加) ### 3. Token使用场景 #### 场景1:小程序端获取用户信息 **文件位置:** `book/src/main/java/com/zhentao/controller/app/AppUserController.java` **方法:** `getCurrentUserInfo()` - 第26-47行 ```java @GetMapping("/info") public Result getCurrentUserInfo(@RequestHeader(value = "Authorization", required = false) String token) { // 1. 检查Token格式 if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) { return Result.error(401, "未登录"); } try { // 2. 提取JWT Token String jwt = token.substring(7); // 3. 从Token中获取用户ID Integer userId = jwtUtil.getUserIdFromToken(jwt); if (userId == null) { return Result.error(401, "Token无效"); } // 4. 查询用户信息 User user = userService.getById(userId); if (user == null) { return Result.error(404, "用户不存在"); } // 5. 清除敏感信息 user.setPassword(null); return Result.success(user); } catch (Exception e) { return Result.error(401, "Token解析失败"); } } ``` **请求头格式:** `Authorization: Bearer ` --- ## 代码文件位置汇总 ### 核心文件 | 功能 | 文件路径 | 说明 | |------|---------|------| | **实体类** | `book/src/main/java/com/zhentao/pojo/User.java` | 用户实体,对应user表 | | **Mapper接口** | `book/src/main/java/com/zhentao/mapper/UserMapper.java` | 数据访问层 | | **Service接口** | `book/src/main/java/com/zhentao/service/UserService.java` | 业务逻辑接口 | | **Service实现** | `book/src/main/java/com/zhentao/service/impl/UserServiceImpl.java` | **核心业务逻辑**(注册、登录、密码加密) | | **普通用户Controller** | `book/src/main/java/com/zhentao/controller/UserAuthController.java` | 用户注册、登录接口 | | **管理员Controller** | `book/src/main/java/com/zhentao/controller/AdminController.java` | 管理员登录接口 | | **应用端Controller** | `book/src/main/java/com/zhentao/controller/app/AppUserController.java` | 小程序端用户信息接口 | | **密码加密配置** | `book/src/main/java/com/zhentao/config/SecurityConfig.java` | BCrypt编码器配置 | | **JWT工具类** | `book/src/main/java/com/zhentao/common/JwtUtil.java` | JWT Token生成和验证 | | **配置文件** | `book/src/main/resources/application.yml` | JWT密钥和过期时间配置 | ### 辅助工具 | 文件路径 | 说明 | |---------|------| | `book/src/main/java/com/zhentao/util/PasswordGenerator.java` | 密码加密工具类(用于生成BCrypt密码) | | `book/src/main/java/com/zhentao/controller/PasswordController.java` | 密码加密API(管理员工具) | ### 前端相关 | 文件路径 | 说明 | |---------|------| | `xiao/api/user.js` | 小程序端用户API封装 | | `xiao/pages/login/login.js` | 小程序登录页面 | | `xiao/pages/register/register.js` | 小程序注册页面 | | `xiao/utils/api.js` | API请求工具(包含Token处理) | --- ## 安全机制总结 ### 1. 密码安全 - ✅ **BCrypt加密存储**:密码以哈希形式存储,无法逆向 - ✅ **自动加盐**:每次加密结果不同,防止彩虹表攻击 - ✅ **自动升级**:旧数据明文密码自动升级为BCrypt ### 2. 身份认证 - ✅ **JWT Token**:无状态认证,适合分布式系统 - ✅ **Token过期机制**:默认24小时过期 - ✅ **角色分离**:管理员和普通用户使用不同登录接口 ### 3. 权限控制 - ✅ **角色验证**:登录时检查用户角色 - ✅ **状态检查**:禁用账号无法登录 - ✅ **接口隔离**:管理员和普通用户接口分离 --- ## 常见问题 ### Q1: 如何重置密码? **方法1:** 使用密码加密工具API ``` GET /api/admin/password/encode?password=新密码 ``` (需要管理员登录且完成人脸验证) **方法2:** 使用PasswordGenerator工具类生成BCrypt密码,然后执行SQL更新 ### Q2: 如何查看密码加密后的值? 查看数据库 `user` 表的 `password` 字段,BCrypt加密后的密码以 `$2a$10$` 开头。 ### Q3: 为什么登录失败? 可能原因: 1. 用户名或密码错误 2. 密码未加密(旧数据),需要升级 3. 账号被禁用(status = 0) 4. 管理员通过小程序端登录(被禁止) ### Q4: Token过期怎么办? 重新登录获取新的Token。Token默认24小时过期,可在 `application.yml` 中修改 `jwt.expiration` 配置。 --- ## 数据库操作示例 ### 查看用户信息 ```sql SELECT user_id, username, password, user_role, status, create_time FROM user WHERE username = 'testuser'; ``` ### 更新密码(BCrypt加密后) ```sql UPDATE user SET password = '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy' WHERE username = 'testuser'; ``` ### 启用/禁用用户 ```sql -- 禁用用户 UPDATE user SET status = 0 WHERE user_id = 1; -- 启用用户 UPDATE user SET status = 1 WHERE user_id = 1; ``` --- **文档版本:** v1.0 **最后更新:** 2024年