126 lines
3.2 KiB
Markdown
126 lines
3.2 KiB
Markdown
# 06 — 安全与日志规则
|
||
|
||
> **适用**:所有后端开发任务。
|
||
|
||
---
|
||
|
||
## 安全规则
|
||
|
||
### 认证检查(不变式)
|
||
|
||
每个需要登录的接口必须通过 Shiro 认证。JeecgBoot 默认全局拦截。
|
||
|
||
```java
|
||
// JeecgBoot 认证机制:
|
||
// 1. 所有 /sys/** /admin/** 默认需要登录
|
||
// 2. 白名单在 application.yml 配置
|
||
// 3. 权限注解:@RequiresPermissions / @RequiresRoles
|
||
|
||
// ✅ 接口加权限
|
||
@RequiresPermissions("user:add")
|
||
@PostMapping("/add")
|
||
public Result<?> add(...) { ... }
|
||
|
||
// ❌ 不要为了调试临时关认证
|
||
// ❌ 不要把需要认证的路径加到 shiro.excludeUrls
|
||
```
|
||
|
||
### 数据权限(租户隔离)
|
||
|
||
JeecgBoot 已通过 MyBatis-Plus 拦截器自动注入 tenant_id,**但如果手写 SQL 必须手动加**。
|
||
|
||
```java
|
||
// ✅ 用 MyBatis-Plus 自动处理
|
||
taskService.list(new LambdaQueryWrapper<Task>().eq(Task::getStatus, "pending"));
|
||
|
||
// ❌ 手写 SQL 忘了加租户 → 租户A 看到租户B 的数据
|
||
@Select("SELECT * FROM task WHERE status = #{status}")
|
||
|
||
// ✅ 手写 SQL 正确写法
|
||
@Select("SELECT * FROM task WHERE status = #{status} AND tenant_id = #{tenantId}")
|
||
```
|
||
|
||
### 敏感信息保护
|
||
|
||
```java
|
||
// ✅ 密码字段不返回
|
||
@Entity
|
||
public class SysUser {
|
||
@JsonIgnore // ← 关键
|
||
private String password;
|
||
}
|
||
|
||
// ✅ 手机号脱敏返回(VO 层处理)
|
||
userVO.setPhone(phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"));
|
||
```
|
||
|
||
### 文件上传安全
|
||
|
||
```java
|
||
// ✅ 必须校验(JeecgBoot 已内置部分,确认开启)
|
||
- 文件类型白名单:只允许 jpg/png/pdf/doc/xlsx
|
||
- 文件大小上限:单文件 ≤ 10MB
|
||
- 文件名处理:用 UUID 重命名,防路径穿越
|
||
- 存储路径:不暴露原始文件名给前端
|
||
```
|
||
|
||
### 接口防刷
|
||
|
||
```java
|
||
// ✅ 敏感接口(登录/短信/支付)加限流
|
||
// JeecgBoot 使用 Sentinel 或自定义注解
|
||
@RateLimiter(count = 5, time = 60) // 60秒内最多5次
|
||
@PostMapping("/send-sms")
|
||
public Result<?> sendSms(...) { ... }
|
||
```
|
||
|
||
---
|
||
|
||
## 日志规则
|
||
|
||
### 日志级别
|
||
|
||
| 级别 | 场景 |
|
||
|------|------|
|
||
| ERROR | 数据库连不上、第三方接口失败、业务异常需要人工介入 |
|
||
| WARN | 降级处理、重试成功、配置缺失但有默认值 |
|
||
| INFO | 用户登录、任务创建、状态变更、定时任务执行 |
|
||
| DEBUG | 开发调试用,不入生产 |
|
||
|
||
### 日志内容
|
||
|
||
```java
|
||
// ✅ 关键操作
|
||
log.info("任务状态变更 taskId={} from={} to={} operator={}", taskId, oldStatus, newStatus, userId);
|
||
|
||
// ✅ 异常
|
||
log.error("调用第三方接口失败 url={}", url, e); // 最后一个参数是异常对象
|
||
|
||
// ❌ 不要这样
|
||
log.info("进来了");
|
||
log.info("user=" + user.toString()); // 可能打印密码
|
||
System.out.println("debug: " + xxx); // 永远不
|
||
```
|
||
|
||
### 禁止
|
||
|
||
| ❌ | 原因 |
|
||
|----|------|
|
||
| 循环里打 INFO | 日志爆炸 |
|
||
| 打印完整大对象 | 刷屏且可能泄露敏感数据 |
|
||
| 用 System.out.println | 不入日志文件 |
|
||
| 用 e.printStackTrace() | 不入日志框架 |
|
||
|
||
|
||
---
|
||
|
||
## 检查表
|
||
|
||
- [ ] 新接口需要认证(不在白名单中)
|
||
- [ ] 手写 SQL 加了租户隔离
|
||
- [ ] 敏感字段不返回给前端
|
||
- [ ] 文件上传有类型/大小校验
|
||
- [ ] 关键操作有 INFO 日志
|
||
- [ ] 异常有 ERROR 日志(带栈)
|
||
- [ ] 无硬编码密码/密钥
|