diff --git a/Redis安全防护备忘.md b/Redis安全防护备忘.md new file mode 100644 index 0000000..a97d7f4 --- /dev/null +++ b/Redis安全防护备忘.md @@ -0,0 +1,80 @@ +# Redis 安全防护备忘 + +## 事件回顾 + +2026-07-31,生产环境第三方接口报 "Redis服务异常",排查发现: + +1. Redis 容器(daycard-redis)被外部设置为 `replicaof 109.244.159.27 29846`(从节点) +2. master 不可达,Redis 进入只读状态,写入操作全部失败 +3. PartnerSignFilter 的 nonce 防重放(`setIfAbsent`)写入被拒,导致所有第三方接口不可用 + +## 根因 + +Redis 未授权访问被攻击: + +- **端口暴露公网**:docker-compose.yml 中 `ports: "6379:6379"` 将 Redis 暴露到 0.0.0.0:6379 +- **无密码**:REDIS_PASSWORD 为空,任何人可直连 Redis +- 攻击者扫描公网 6379 端口,未授权连接后执行 `CONFIG SET replicaof 109.244.159.27 29846`,将 Redis 变为从节点 + +## 防护措施(已落实到 docker-compose.yml) + +### 1. 不暴露端口到公网 + +去掉 Redis 的 `ports` 映射,仅在 docker 内网访问。应用容器通过 `redis:6379` 访问,无需宿主机端口映射。 + +### 2. 加密码 + +``` +command: redis-server --requirepass AsRedis2026!Safe +``` + +应用环境变量同步配置 `REDIS_PASSWORD=AsRedis2026!Safe`。 + +### 3. 强制单机模式 + +``` +command: redis-server --requirepass AsRedis2026!Safe --replicaof no one +``` + +`--replicaof no one` 确保即使被连上也无法被设置为从节点。 + +## 重新部署步骤 + +```bash +# 1. 拉取最新代码(含修改后的 docker-compose.yml) +cd /path/to/as-daycard-openapi +git pull + +# 2. 重建 Redis 容器(彻底清除被篡改的配置) +docker rm -f daycard-redis + +# 3. 启动新的 Redis 容器 +docker-compose up -d redis + +# 4. 验证:replicaof 应为空 +docker exec daycard-redis redis-cli -a 'AsRedis2026!Safe' config get replicaof +# 预期输出: +# 1) "replicaof" +# 2) "" + +# 5. 验证:密码生效(无密码连接应失败) +docker exec daycard-redis redis-cli config get replicaof +# 预期输出:NOAUTH Authentication required. + +# 6. 重启应用容器(加载新密码) +docker-compose up -d daycard-openapi + +# 7. 验证应用正常 +curl http://localhost:8084/openapi/partner/media/package \ + -H "appKey: 你的appKey" \ + -H "timestamp: $(date +%s%3N)" \ + -H "nonce: test123" \ + -H "sign: 签名" +``` + +## 日常运维注意事项 + +- **不要恢复 6379 端口映射**:如需宿主机调试 Redis,用 `docker exec -it daycard-redis redis-cli -a 'AsRedis2026!Safe'` 进入 +- **密码保密**:`AsRedis2026!Safe` 已写在 docker-compose.yml 中,如需修改请同步更新 Redis command 和应用 REDIS_PASSWORD 环境变量 +- **定期检查**:`docker exec daycard-redis redis-cli -a 'AsRedis2026!Safe' config get replicaof` 确认未被篡改 +- **服务器安全**:检查 /root/.ssh/authorized_keys 是否有陌生 key,确认未被 SSH 入侵 diff --git a/docker-compose.yml b/docker-compose.yml index 62c3cef..6493c38 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: - DB_PASSWORD=MAe8pALGWjE6jCwi - REDIS_HOST=redis - REDIS_PORT=6379 + - REDIS_PASSWORD=AsRedis2026!Safe - REDIS_OPEN=true - JWT_SECRET=2F064BA10037477FBB3A1BF9D508AE96 - JWT_EXPIRE=604800 @@ -37,6 +38,6 @@ services: redis: image: redis:7-alpine container_name: daycard-redis - ports: - - "6379:6379" + command: redis-server --requirepass AsRedis2026!Safe --replicaof no one + # 不映射端口到宿主机,仅在 docker 内网访问,防止公网未授权访问 restart: unless-stopped diff --git a/src/main/java/io/as/modules/jst/controller/ApiJstCallLogController.java b/src/main/java/io/as/modules/jst/controller/ApiJstCallLogController.java new file mode 100644 index 0000000..79efb20 --- /dev/null +++ b/src/main/java/io/as/modules/jst/controller/ApiJstCallLogController.java @@ -0,0 +1,37 @@ +package io.as.modules.jst.controller; + +import io.as.common.utils.PageUtils; +import io.as.common.utils.R; +import io.as.modules.jst.service.ApiJstCallLogService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * 橘视厅调用日志管理(管理后台,JWT鉴权) + *

记录调用橘视厅免密登录、卡密激活、会员查询等接口的出站请求与响应

+ */ +@Api(tags = "橘视厅调用日志") +@RestController +@RequestMapping("/manage/jst/call-log") +public class ApiJstCallLogController { + + @Autowired + private ApiJstCallLogService apiJstCallLogService; + + @ApiOperation("分页查询橘视厅调用日志") + @GetMapping("/list") + public R list(@RequestParam Map params) { + PageUtils page = apiJstCallLogService.queryPage(params); + Map data = new HashMap<>(); + data.put("page", page); + return R.dataSuccess(data); + } +} diff --git a/src/main/java/io/as/modules/sys/controller/AuditLogController.java b/src/main/java/io/as/modules/sys/controller/AuditLogController.java new file mode 100644 index 0000000..c0ff34c --- /dev/null +++ b/src/main/java/io/as/modules/sys/controller/AuditLogController.java @@ -0,0 +1,37 @@ +package io.as.modules.sys.controller; + +import io.as.common.utils.PageUtils; +import io.as.common.utils.R; +import io.as.modules.sys.service.AuditLogService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * 审计日志管理(管理后台,JWT鉴权) + *

记录平台运营操作(分配/销售/转合同/绑定/解绑/激活/采购等)的审计轨迹

+ */ +@Api(tags = "审计日志") +@RestController +@RequestMapping("/manage/audit-log") +public class AuditLogController { + + @Autowired + private AuditLogService auditLogService; + + @ApiOperation("分页查询审计日志") + @GetMapping("/list") + public R list(@RequestParam Map params) { + PageUtils page = auditLogService.queryPage(params); + Map data = new HashMap<>(); + data.put("page", page); + return R.dataSuccess(data); + } +}