10 KiB
10 KiB
Week 5:Spring 全家桶核心组件 —— 安全、缓存、监控与进阶
目标:在 Week 4 的全栈项目基础上,引入 Spring Security + JWT 实现认证授权,集成 Redis 缓存,接入 Actuator 监控,自定义校验注解,掌握 MyBatis-Plus 逻辑删除与分页。
前置:已完成 Week 4 的 Spring Boot + ORM 双轨 + MVC 全栈项目。
学习成果:一个完整的"学生管理系统 v2",具备:
- JWT 令牌认证(登录 / 注册)
- RBAC 角色控制(ADMIN 可增删改,USER 只读)
- Redis 缓存加速查询
- Actuator 健康检查与监控
- @ValidScore 自定义校验注解
- MyBatis-Plus 逻辑删除
Day 1:Spring Security 架构 & 过滤器链
知识点:
- Spring Security 核心概念:Authentication(认证)vs Authorization(授权)
- SecurityFilterChain:过滤器链的工作原理
- 关键过滤器:UsernamePasswordAuthenticationFilter、SecurityContextHolder
- 无状态会话(STATELESS)vs 有状态会话
- CSRF 的概念及何时禁用(前后端分离 API)
- BCrypt 密码编码器的不可逆特性
动手 → 理解:
- 阅读
SecurityConfig.java:逐行理解每条配置的意图 - 用 Postman / 浏览器测试:不登录直接访问
/api/students,观察 401 响应 - 去掉
@Bean的 SecurityFilterChain,对比默认行为(表单登录页) - 尝试把
sessionCreationPolicy改回IF_REQUIRED,观察 JSESSIONID 的出现
核心文件:
config/SecurityConfig.java— 安全配置入口service/AuthService.java— 认证逻辑(login / register)
思考题:为什么要用 requestMatchers(...).permitAll() 放行 /api/auth/**?不放行会怎样?
Day 2:JWT 认证 —— 登录 / 注册
知识点:
- JWT(JSON Web Token)结构:Header.Payload.Signature
- 对称密钥签名:HMAC-SHA256
- JJWT 库的使用:
Jwts.builder()生成,Jwts.parser().verifyWith()验证 - Claims:subject、issuedAt、expiration、自定义 claim(role)
- 前端 Token 存储:localStorage vs sessionStorage vs Cookie 的权衡
- OncePerRequestFilter:为什么用它而不是普通 Filter
动手 → 理解:
- 用浏览器 DevTools Application 面板查看 localStorage 中的
wk5_token - 把 token 粘贴到 jwt.io 在线解析,观察 payload 内容
- 修改
application.yml中jwt.expiration为 60000(1分钟),等一分钟再刷新页面 - 手动篡改 localStorage 中 token 的某一个字符,观察 401 错误
核心文件:
security/JwtUtil.java— Token 生成与解析工具security/JwtAuthFilter.java— 过滤器:从请求头提取并验证 Tokenconfig/DataInitializer.java— 启动时创建默认用户
思考题:JWT 的三个部分中,哪个是"不可伪造"的关键?为什么?
Day 3:RBAC 方法级安全 —— 权限控制
知识点:
- RBAC(Role-Based Access Control):角色 → 权限 → 资源
@EnableMethodSecurity开启方法级注解@PreAuthorize("hasRole('ADMIN')")的 SpEL 表达式- SecurityContextHolder:如何在 Controller 中获取当前用户
Principal参数注入- 权限不足时的 AccessDeniedException → 全局异常处理返回 403
动手 → 理解:
- 用 user/123456 登录,尝试新增学生,观察后台日志中的 AccessDeniedException
- 在前端查看:USER 角色的"新增"按钮为何消失(app.js 中的
checkRoleUI()) - 在
StudentController.java的deleteStudent方法上暂时注释掉@PreAuthorize,用 USER 登录后通过 fetch 发送 DELETE 请求验证 SecurityConfig 的 URL 级别拦截 - 登录 admin 后访问
/api/students/me,查看返回的 Principal 信息
核心文件:
controller/StudentController.java→@PreAuthorize注解exception/GlobalExceptionHandler.java→ AccessDeniedException 处理
思考题:URL 级别拦截(SecurityConfig)和方法级拦截(@PreAuthorize)各有什么适用场景?
Day 4:Redis 缓存集成
知识点:
- Spring Cache 抽象:
@EnableCaching、@Cacheable、@CacheEvict - Redis 作为缓存后端 vs 默认 ConcurrentHashMap
- 缓存 Key 的 SpEL 表达式:
#id、'list' @CacheEvict(allEntries = true)的使用场景和风险- application.yml 中的 Redis 连接配置
- Redis 命令行基本操作:
KEYS *、GET key、DEL key
动手 → 理解:
- 启动项目,访问
/api/students两次,观察第二次的响应速度变化 - 用
redis-cli KEYS *查看 Spring Cache 生成的 Key 格式 - 用
redis-cli GET "students::list"查看缓存的序列化数据 - 新增一个学生后,再次
KEYS *观察缓存是否被清空 - 暂时关掉 Redis 服务,观察应用是否还能正常启动(
spring.cache.type: none)
核心文件:
service/jpa/StudentJpaService.java— JPA 服务的缓存注解service/mp/StudentMpService.java— MP 服务的缓存注解application.yml— Redis 与 Cache 配置
思考题:为什么新增/修改/删除时要 allEntries = true 清理全部缓存,而不是只清理单条?
Day 5:Actuator 应用监控
知识点:
- Spring Boot Actuator 的端点(Endpoints):health、info、beans、env、mappings
/actuator/health:应用健康状态(UP / DOWN)/actuator/beans:Spring 容器中所有 Bean 的列表和依赖关系/actuator/env:运行时环境变量和配置属性/actuator/mappings:所有 URL 映射(Controller + Filter)- 生产环境中端点的安全控制
动手 → 理解:
- 浏览器访问
http://localhost:8080/actuator查看所有可用端点 - 访问
/actuator/health,观察 JSON 中的status: "UP" - 访问
/actuator/beans,搜索 "jwt" 或 "security" 找到相关 Bean - 访问
/actuator/mappings,找到自己写的 StudentController 的映射信息 - 在
application.yml中把include改为只保留health,info,重启再访问/actuator
核心文件:
application.yml→management.endpoints.web.exposure.includepom.xml→spring-boot-starter-actuator
思考题:为什么不应该在生产环境暴露 /actuator/env?里面会有什么敏感信息?
Day 6:自定义校验注解 —— @ValidScore
知识点:
- Bean Validation(JSR 380)的扩展机制
@Constraint(validatedBy = ...)元注解ConstraintValidator<A, T>接口:initialize()+isValid()- 自定义注解的三要素:
message、groups、payload ConstraintValidatorContext自定义错误消息@ValidScore+@NotNull的配合使用
动手 → 理解:
- 阅读
ScoreValidator.java的isValid方法,理解 null 的处理逻辑 - 在前端新增学生时,输入成绩 150 并提交,观察后端返回的校验错误
- 修改
@ValidScore的message默认值为中文提示,观察返回的 JSON - 尝试把
@ValidScore应用到Student实体的score字段(DTO 校验 vs 实体校验的区别) - 动手写一个新的自定义注解
@ValidEmail,校验邮箱格式
核心文件:
annotation/ValidScore.java— 自定义注解定义annotation/ScoreValidator.java— 校验逻辑实现
思考题:isValid 中对 null 返回 true,而不做 null 判断。如果调用方忘记加 @NotNull,会发生什么?
Day 7:MyBatis-Plus 进阶 —— 逻辑删除 & LambdaQueryWrapper
知识点:
- 逻辑删除 vs 物理删除:概念差异与适用场景
@TableLogic:标记逻辑删除字段mybatis-plus.global-config.db-config.logic-delete-field全局配置- 逻辑删除的 SQL 行为:DELETE → UPDATE SET deleted=1;SELECT 自动追加 deleted=0
- JPA 如何手动实现逻辑删除(@Query 中添加
s.deleted = 0) - LambdaQueryWrapper 条件构造:
.eq()、.like()、.orderByDesc()、.between() - 分页拦截器 PaginationInnerInterceptor 的
overflow和maxLimit配置
动手 → 理解:
- 用 XML (MyBatisPlusConfig) 配置逻辑删除 vs 用
@TableLogic注解的效果对比 - 在 MySQL 中执行
SELECT * FROM student WHERE deleted = 1,验证逻辑删除后的数据是否存在 - 对比 MP 服务(自动过滤 deleted)和 JPA 服务(手动写 deleted=0 条件)的实现差异
- 尝试修改
maxLimit为 5,观察分页查询被限制的效果 - 在 LambdaQueryWrapper 上尝试组合条件:
like(Student::getName, keyword).between(Student::getAge, 18, 25)
核心文件:
config/MyBatisPlusConfig.java— 分页拦截器 + 逻辑删除全局配置service/mp/StudentMpService.java— MP 服务的 LambdaQueryWrapper 使用service/jpa/StudentJpaService.java— JPA 手动逻辑删除的 @Query 写法application.yml→mybatis-plus.global-config.db-config
思考题:逻辑删除和物理删除各适用于什么业务场景?为什么 Week 5 选择了逻辑删除?
Week 5 总结
| 维度 | Week 4(v1) | Week 5(v2) |
|---|---|---|
| 认证 | 固定 Token 字符串 | JWT 动态签发 + 过期控制 |
| 授权 | 无角色概念 | RBAC(ADMIN / USER) |
| 安全框架 | 手写 Interceptor | Spring Security + FilterChain |
| 缓存 | 无 | Redis + Spring Cache 注解 |
| 监控 | 无 | Actuator 端点 |
| 校验 | @Min/@Max 内置注解 | 自定义 @ValidScore |
| 数据库 | MP/JPA 直接操作 | MP 逻辑删除 + JPA 手动兼容 |
环境保障:
- MySQL 8.0+,数据库
gc_plan,表student+users - Redis 需运行在 localhost:6379(默认端口)
DataInitializer在首次启动时自动创建 admin/123456 和 user/123456- 如需重置:删除 users 表中记录,重启应用即可重新初始化
附加练习:
- 给 USER 角色新增一个权限:可以查看但不能修改自己的详细信息
- 用 Redis 缓存 JWT Token,实现"黑名单"注销功能
- 在 Actuator 中增加自定义 HealthIndicator(检查 Redis 连接状态)
- 把 @ValidScore 扩展为支持不同学科的不同分数范围