用户认证与数据持久化底层原理
在商城项目的阶段二开发中,核心任务是实现用户注册与登录,并建立安全的网络通信通道。本篇笔记详细梳理了这其中的核心技术和底层原理。
一、 MyBatis-Plus 的数据持久化原理
MyBatis-Plus (简称 MP) 是对 MyBatis 的无侵入增强工具,其核心原理是通过 Java 反射机制 与 注解声明 自动完成对象关系映射 (ORM)。
1. 实体与数据库的映射
MP 通过在实体类上添加注解来识别数据库映射规则:
@TableName("users"):指定该类对应的数据库表为users。BaseEntity中的主键属性上标注了@TableId(type = IdType.AUTO),声明主键为自增类型。- 类中的驼峰属性(如
createTime)会被 MP 的自动驼峰命名规则 (map-underline-to-camel-case: true) 自动映射为下划线形式的数据库列名(create_time)。
2. BaseMapper 的通用 CRUD 注入
@Mapper
public interface UserMapper extends BaseMapper<User> {}
在 Spring Boot 启动时,MP 框架会扫描继承了 BaseMapper<T> 的接口,并根据泛型 T (即 User 实体类) 通过反射获取其所有字段,在内存中动态生成一系列常用的 SQL 语句(如 INSERT INTO users ...)。
当我们在 Service 中调用 baseMapper.insert(user) 时,底层的 MyBatis 执行器会执行插入并使用 JDBC 的 getGeneratedKeys 方法,将数据库生成的自增 ID 回填到传入 of user 对象的 id 属性中。
二、 密码加密存储与 BCrypt 算法
数据库绝对不能明文存储用户的密码。青橙商城采用 BCrypt 强哈希算法对密码进行加密。
1. 为什么不使用 MD5 或 SHA-256?
MD5 和 SHA-256 是快速哈希算法,极易受到**彩虹表(Rainbow Table)**的碰撞攻击和 GPU 暴力破解。而 BCrypt 是单向、慢速哈希算法,能显著增加破解成本。
2. BCrypt 的核心原理
- 自动加盐(Salt):BCrypt 每次加密时都会在底层随机生成一个唯一的“盐值”(Salt),并与密码拼合。这意味着:相同的明文密码,每次加密得到的密文完全不同。
- 结构化密文:密文形如
$2a$10$DnPNUNqh...。$2a$代表算法版本。$10$代表计算强度(即哈希迭代次数为 $2^{10}$ 次)。- 随后的前 22 个字符是随机生成的盐值。
- 剩余字符是混合盐值后哈希生成的密文。
3. 代码验证逻辑
- 加密:
user.setPassword(passwordEncoder.encode(password)); - 校验:由于每次生成的密文不同,不能直接用
=比较。必须用 matches 方法:
BCrypt 会从passwordEncoder.matches(plainPassword, hashedPassword);hashedPassword中读取当初的加密盐值,用这个盐值对plainPassword进行哈希,最后比对生成的密文。
三、 JWT (JSON Web Token) 无状态认证原理
在前后端分离架构下,不再推荐使用传统的 Session-Cookie 机制,而是采用基于 JWT 的无状态身份验证。
1. JWT 的三部分组成
JWT 是一个由 . 连接的三段式 Base64 字符串:
- Header(头部):描述 Token 的元数据,通常为签名算法名称(如 HS384)和令牌类型(JWT)。
- Payload(载荷):存放声明数据(Claims),包括用户名(
sub)、签发时间(iat)、过期时间(exp)等非敏感用户信息。 - Signature(签名):由
Header与Payload的 Base64 字符串拼合后,使用服务器保密的 Key 和算法进行哈希计算生成的签名。
2. 无状态的生命周期
- 用户在前端输入用户名和密码登录。
- 后端校验密码通过后,使用私钥签名生成 JWT 字符串,并返回给前端。
- 前端将 JWT 存储在 LocalStorage 中,并载入状态管理(Pinia/Vuex)。
- 前端此后的每次 HTTP 请求,都在 Header 中携带
Authorization: Bearer <Token>。 - 后端接收到请求后解析 Token,使用本地私钥验证 Signature。无需查询数据库或 Redis 验证 Session,直接在内存中判定 Token 是否合法。
四、 跨域代理(CORS)与请求转发
由于浏览器的同源策略(Same-Origin Policy),运行在 http://localhost:3000 的管理端前端直接向 http://localhost:8080 的后端发送请求会被拦截。本项目在开发阶段使用 Vite 的服务器代理功能解决跨域。
1. Axios 封装与基地址
在管理端 request.ts 中,配置 Axios 的统一前缀为 /api:
const service = axios.create({
baseURL: '/api',
timeout: 10000
});
2. Vite 代理转发
在 vite.config.ts 中,配置 dev 服务器代理:
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
- 工作流:前端请求发送至
http://localhost:3000/api/auth/login。 - 代理拦截:Vite 开发服务器发现路径以
/api开头,便代为将请求转发至后端的http://localhost:8080/api/auth/login。 - 避开跨域:因为浏览器是与同源的
localhost:3000通信,而代理服务器与后端服务器之间的请求不受同源策略限制,从而绕过了跨域限制。