From 7474d9b4e7d40b1bdad17e9db4db0d830a284546 Mon Sep 17 00:00:00 2001 From: malei <> Date: Sun, 2 Aug 2026 03:28:09 +0800 Subject: [PATCH] docs: define strict session expiry boundary --- graph/ontology.jsonl | 8 +++---- ontology/rules.md | 7 +++--- ...决策-SessionToken严格过期边界-2026-08-02.md | 22 +++++++++++++++++++ 生产发布与回滚Runbook-2026-08-01.md | 4 +++- 4 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 架构决策-SessionToken严格过期边界-2026-08-02.md diff --git a/graph/ontology.jsonl b/graph/ontology.jsonl index ded84b1..6e5ee0f 100644 --- a/graph/ontology.jsonl +++ b/graph/ontology.jsonl @@ -25,7 +25,7 @@ {"id":"action:unsubscribe","type":"Action","name":"unsubscribe","nameZh":"一键退订","inputs":["unsubscribeToken"],"outputs":["entity:follow_up_task"],"entrypoints":["POST /api/report/unsubscribe","ReportLeadController#unsubscribe"],"appliesRules":["rule:BR-UNSUB-001","rule:BR-FU-001"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#unsubscribe; backend/src/main/java/com/petstore/service/FollowUpTaskService.java#cancelForUnsubscribe","evidence":"anchored","domain":"lead","test":"ReportLeadControllerTest; FollowUpTaskServiceTest"} {"id":"action:list_leads","type":"Action","name":"listLeads","nameZh":"回访池列表","inputs":["status"],"outputs":["entity:report_lead"],"entrypoints":["GET /api/report/leads","ReportLeadController#leads"],"appliesRules":["rule:BR-LEAD-002","rule:BR-LEAD-003"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#leads","evidence":"anchored","test":"ReportLeadControllerTest","domain":"lead"} {"id":"event:store_registered","type":"Event","name":"StoreRegistered","nameZh":"门店入驻","domain":"store","payload":["storeId","bossUserId","occurredAt"],"emittedBy":["action:register_boss"],"code":"backend/src/main/java/com/petstore/service/BusinessEventService.java#recordStoreRegistered","test":"BusinessEventServiceTest","evidence":"anchored","note":"持久化 BusinessEvent 与 AuditLog,均不含手机号或 legacy inviteCode"} -{"id":"event:session_issued","type":"Event","name":"SessionIssued","nameZh":"签发会话令牌","domain":"auth","payload":["userId","storeId","role","exp"],"emittedBy":["action:register_boss","action:login","action:wx_phone_login","action:accept_staff_invitation"],"code":"backend/src/main/java/com/petstore/auth/SessionTokenService.java#issue","test":"SessionTokenServiceTest","evidence":"anchored"} +{"id":"event:session_issued","type":"Event","name":"SessionIssued","nameZh":"签发会话令牌","domain":"auth","payload":["userId","storeId","role","exp"],"emittedBy":["action:register_boss","action:login","action:wx_phone_login","action:accept_staff_invitation"],"code":"backend/src/main/java/com/petstore/auth/SessionTokenService.java#issue","doc":"docs/架构决策-SessionToken严格过期边界-2026-08-02.md","test":"SessionTokenServiceTest","evidence":"anchored","note":"exp 为必填未来 Unix 秒时间戳;缺失、非法、非正或到期均拒绝"} {"id":"event:wx_bound","type":"Event","name":"WxBound","nameZh":"微信标识绑定","domain":"auth","payload":["userId","openid","unionid"],"emittedBy":["action:bind_wechat"],"code":"backend/src/main/java/com/petstore/service/UserService.java#bindWechatMiniIdentity","evidence":"anchored"} {"id":"event:appointment_created","type":"Event","name":"AppointmentCreated","nameZh":"预约创建","domain":"appointment","payload":["storeId","storeCustomerId","aggregateId","actorUserId","actorRole","bookingOrigin","durationMinutes","occurredAt"],"emittedBy":["action:create_appointment"],"appliesRules":["rule:BR-BE-001","rule:BR-APPT-004"],"code":"backend/src/main/java/com/petstore/service/AppointmentService.java#createBooking; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordAppointmentCreated","test":"AppointmentServiceTest; BusinessEventServiceTest","evidence":"anchored"} {"id":"event:appointment_status_changed","type":"Event","name":"AppointmentStatusChanged","nameZh":"预约状态迁移","domain":"appointment","payload":["storeId","storeCustomerId","aggregateId","fromStatus","toStatus","actorUserId","actorRole","occurredAt"],"emittedBy":["action:start_service","action:transition_appointment_status","action:submit_report"],"appliesRules":["rule:BR-APPT-001","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/AppointmentService.java#startService; #transitionStatus; backend/src/main/java/com/petstore/service/ReportService.java#create; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordAppointmentStatusChanged","test":"AppointmentServiceTest; ReportServiceTest#createDoingAppointmentMarksDone; BusinessEventServiceTest","evidence":"anchored"} @@ -36,7 +36,7 @@ {"id":"event:report_opened","type":"Event","name":"ReportOpened","nameZh":"报告首次打开","domain":"report","payload":["storeId","storeCustomerId","reportId","visitType","occurredAt"],"emittedBy":["action:track_report_open"],"appliesRules":["rule:BR-RPT-007","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#trackReportOpen; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordReportOpenByToken","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","test":"ReportControllerTest; BusinessEventServiceTest","evidence":"anchored"} {"id":"event:report_reopened","type":"Event","name":"ReportReopened","nameZh":"报告再次打开","domain":"report","payload":["storeId","storeCustomerId","reportId","visitType","occurredAt"],"emittedBy":["action:track_report_open"],"appliesRules":["rule:BR-RPT-007","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/BusinessEventService.java#recordReportOpenByToken","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","test":"BusinessEventServiceTest#reportOpenStoresReportIdButNeverToken","evidence":"anchored"} {"id":"event:lead_submitted","type":"Event","name":"LeadSubmitted","nameZh":"留资提交","domain":"lead","payload":["storeId","storeCustomerId","leadId","repeatSubmit","occurredAt"],"emittedBy":["action:submit_lead"],"appliesRules":["rule:BR-LEAD-001","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/ReportLeadService.java#submit; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordLeadSubmitted","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","test":"ReportLeadServiceStoreCustomerTest; BusinessEventMigrationTest","evidence":"anchored"} -{"id":"rule:BR-AUTH-001","type":"Rule","name":"HMAC session token + 活跃账号范围鉴权","domain":"auth","appliesTo":["action:update_store","action:delete_store","action:get_store_by_invite_code","action:create_appointment","action:start_service","action:transition_appointment_status","action:get_appointment_detail","action:list_appointments","action:submit_report","action:delete_report","action:list_leads","action:admin_list_leads","action:start_follow_up_task","action:reschedule_follow_up_task","action:close_follow_up_task","action:get_store_onboarding","action:complete_store_onboarding","action:create_staff_invitation","action:list_staff_invitations","action:revoke_staff_invitation","action:list_staff","action:delete_staff"],"code":"backend/src/main/java/com/petstore/auth/AuthInterceptor.java; SessionTokenService.java; CurrentUserContext.java","test":"SessionTokenServiceTest; AuthInterceptorActiveUserTest; AdminFollowUpTaskControllerTest","evidence":"anchored","note":"除签名和过期外,每次受保护请求校验账号仍存在且 role/storeId 与 token 一致,门店角色还须归属有效 Store"} +{"id":"rule:BR-AUTH-001","type":"Rule","name":"HMAC session token + 活跃账号范围鉴权","domain":"auth","appliesTo":["action:update_store","action:delete_store","action:get_store_by_invite_code","action:create_appointment","action:start_service","action:transition_appointment_status","action:get_appointment_detail","action:list_appointments","action:submit_report","action:delete_report","action:list_leads","action:admin_list_leads","action:start_follow_up_task","action:reschedule_follow_up_task","action:close_follow_up_task","action:get_store_onboarding","action:complete_store_onboarding","action:create_staff_invitation","action:list_staff_invitations","action:revoke_staff_invitation","action:list_staff","action:delete_staff"],"code":"backend/src/main/java/com/petstore/auth/AuthInterceptor.java; SessionTokenService.java; CurrentUserContext.java; backend/src/main/java/com/petstore/config/ProductionConfigurationValidator.java","doc":"docs/架构决策-SessionToken严格过期边界-2026-08-02.md","test":"SessionTokenServiceTest; AuthInterceptorActiveUserTest; AdminFollowUpTaskControllerTest; ProductionConfigurationValidatorTest","evidence":"anchored","note":"exp 必须是未来 Unix 秒且非法时 fail-closed;production TTL 为 300~2592000 秒;签名后仍校验活跃账号、role/storeId 和有效门店"} {"id":"rule:BR-AUTH-002","type":"Rule","name":"生产登录策略:仅微信授权","domain":"auth","appliesTo":["action:login","action:wx_phone_login"],"code":"backend/src/main/java/com/petstore/service/UserService.java#login; SmsController.java","test":"UserServiceLoginStrategyTest","evidence":"anchored"} {"id":"rule:BR-AUTH-003","type":"Rule","name":"微信出站敏感日志边界","domain":"auth","appliesTo":["action:wx_phone_login","action:register_boss","action:accept_staff_invitation","action:submit_lead"],"code":"backend/src/main/java/com/petstore/service/WechatMiniProgramService.java; backend/src/main/java/com/petstore/auth/SessionTokenService.java; backend/src/main/java/com/petstore/controller/UserController.java; backend/src/main/java/com/petstore/controller/FileController.java","doc":"docs/架构决策-微信出站调用敏感日志边界-2026-08-02.md","test":"SensitiveLoggingContractTest","evidence":"anchored","note":"异常日志只记 HTTP 状态/微信错误码/异常类型;不记录响应体、手机号、access_token、AppSecret、一次性 code、带 query URL、外部错误文案或完整异常"} {"id":"rule:BR-STORE-001","type":"Rule","name":"店铺更新/删除仅 boss + 同店","domain":"store","appliesTo":["action:update_store","action:delete_store"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#update; #delete","test":"StoreControllerTest","evidence":"anchored"} @@ -94,7 +94,7 @@ {"id":"entity:schedule_block","type":"Entity","name":"ScheduleBlock","nameZh":"门店排班占用","domain":"schedule","fields":["id","store_id","slot_start","duration_minutes","block_type","note","created_by_user_id","create_time","update_time","deleted"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/entity/ScheduleBlock.java; controller/ScheduleController.java; service/ScheduleService.java","doc":"docs/产品设计文档.md §5.2 预约时段占用; docs/架构决策-真实预约容量模型-2026-08-01.md","test":"ScheduleControllerTest; BookingCapacityServiceTest; BookingCapacityMigrationTest","evidence":"anchored"} {"id":"entity:report_image","type":"Entity","name":"ReportImage","nameZh":"报告媒体","domain":"report","fields":["id","report_id","photo_url","photo_type","media_type","sort_order","create_time"],"systems":["Report Media Backend"],"code":"backend/src/main/java/com/petstore/entity/ReportImage.java; mapper/ReportImageMapper.java","doc":"docs/产品设计文档.md §三 t_report_image","test":"ReportServiceTest#createWithImagesSavesEachImage","evidence":"anchored"} {"id":"entity:highlight_video","type":"Entity","name":"HighlightVideo","nameZh":"服务回顾短片","domain":"report","fields":["report_id","highlight_video_url","highlight_video_status","highlight_video_error","highlight_duration_sec","highlight_compose_mode","highlight_fail_reason","highlight_share_cover_url"],"systems":["Report Media Backend"],"code":"backend/src/main/java/com/petstore/entity/Report.java; service/ReportHighlightVideoService.java","doc":"docs/API-报告成片字段契约.md; docs/洗美报告短视频成片方案.md","test":"ReportHighlightVideoServiceTest","evidence":"anchored","jpa":false} -{"id":"entity:session_token","type":"Entity","name":"SessionToken","nameZh":"会话令牌","domain":"auth","fields":["userId","storeId","role","exp"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/auth/SessionTokenService.java; CurrentUser.java; CurrentUserContext.java; AuthInterceptor.java","doc":"docs/ontology/rules.md#rule:BR-AUTH-001","test":"SessionTokenServiceTest","evidence":"anchored","jpa":false} +{"id":"entity:session_token","type":"Entity","name":"SessionToken","nameZh":"会话令牌","domain":"auth","fields":["userId","storeId","role","exp"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/auth/SessionTokenService.java; CurrentUser.java; CurrentUserContext.java; AuthInterceptor.java","doc":"docs/ontology/rules.md#rule:BR-AUTH-001; docs/架构决策-SessionToken严格过期边界-2026-08-02.md","test":"SessionTokenServiceTest","evidence":"anchored","jpa":false,"note":"HMAC-SHA256;exp 必填且必须在未来"} {"id":"action:list_pets","type":"Action","name":"listPets","nameZh":"宠物列表","inputs":[],"outputs":["entity:pet"],"entrypoints":["GET /api/pet/list","PetController#list"],"appliesRules":["rule:BR-PET-001"],"code":"backend/src/main/java/com/petstore/controller/PetController.java#list","evidence":"anchored","test":"PetControllerTest","domain":"pet"} {"id":"action:create_pet","type":"Action","name":"createPet","nameZh":"创建宠物","inputs":["entity:pet"],"outputs":["entity:pet"],"entrypoints":["POST /api/pet/create","PetController#create","PetService#create"],"appliesRules":["rule:BR-PET-001"],"code":"backend/src/main/java/com/petstore/controller/PetController.java#create","evidence":"anchored","test":"PetControllerTest","domain":"pet"} {"id":"action:update_pet","type":"Action","name":"updatePet","nameZh":"更新宠物","inputs":["id","name","petType","breed","avatar","remark"],"outputs":["entity:pet"],"entrypoints":["PUT /api/pet/update","PetController#update","PetService#update"],"appliesRules":["rule:BR-PET-001"],"code":"backend/src/main/java/com/petstore/controller/PetController.java#update","evidence":"anchored","test":"PetControllerTest","domain":"pet"} @@ -139,7 +139,7 @@ {"id":"rule:BR-USER-001","type":"Rule","name":"员工管理与邀请仅 boss + 同店","domain":"user","appliesTo":["action:create_staff","action:list_staff","action:delete_staff","action:create_staff_invitation","action:list_staff_invitations","action:revoke_staff_invitation"],"code":"backend/src/main/java/com/petstore/controller/UserController.java; backend/src/main/java/com/petstore/controller/StaffInvitationController.java","test":"StaffInvitationServiceTest","evidence":"anchored","note":"门店成员可看成员列表;仅 boss 可创建/查看/撤销邀请和删除本店 staff,不能删除 boss"} {"id":"rule:BR-USER-002","type":"Rule","name":"用户更新仅本人","domain":"user","appliesTo":["action:update_user","action:get_user_info"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#updateUser; #info","evidence":"anchored"} {"id":"rule:BR-CONFIG-001","type":"Rule","name":"生产配置环境变量","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml; application-example.yml","doc":"backend/README.md 环境变量表","evidence":"anchored"} -{"id":"rule:BR-CONFIG-002","type":"Rule","name":"production profile 强制只读 schema 安全配置","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml production profile; backend/src/main/java/com/petstore/config/ProductionConfigurationValidator.java; backend/src/main/java/com/petstore/config/CorsConfig.java; backend/src/main/java/com/petstore/PetstoreApplication.java","doc":"backend/README.md production profile; docs/架构决策-全局CORS单一策略-2026-08-02.md","test":"ProductionConfigurationValidatorTest; CorsBoundaryContractTest","evidence":"anchored","note":"生产禁用自动建表/show-sql/SMS 万能码/默认数据初始化,并拒绝弱密钥、占位域名和宽 CORS;全局 CorsFilter 是唯一策略,Controller 禁止局部 @CrossOrigin"} +{"id":"rule:BR-CONFIG-002","type":"Rule","name":"production profile 强制只读 schema 安全配置","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml production profile; backend/src/main/java/com/petstore/config/ProductionConfigurationValidator.java; backend/src/main/java/com/petstore/config/CorsConfig.java; backend/src/main/java/com/petstore/PetstoreApplication.java","doc":"backend/README.md production profile; docs/架构决策-全局CORS单一策略-2026-08-02.md; docs/架构决策-SessionToken严格过期边界-2026-08-02.md","test":"ProductionConfigurationValidatorTest; CorsBoundaryContractTest","evidence":"anchored","note":"生产禁用自动建表/show-sql/SMS 万能码/默认数据初始化,并拒绝弱密钥、越界 session TTL、占位域名和宽 CORS;全局 CorsFilter 是唯一策略,Controller 禁止局部 @CrossOrigin"} {"id":"rule:BR-CONFIG-003","type":"Rule","name":"生产发布只读门禁","domain":"config","appliesTo":[],"code":"backend/src/main/java/com/petstore/config/ProductionDatabasePreflightRunner.java; backend/src/main/java/com/petstore/config/PetstoreRuntimeHealthIndicator.java; backend/deploy/release-preflight.sh; backend/deploy/production-smoke.sh","doc":"docs/生产发布与回滚Runbook-2026-08-01.md; docs/生产监控与告警基线-2026-08-01.md","test":"CorsBoundaryContractTest","evidence":"anchored","note":"备份和八迁移后运行 schema validate + 31 项只读不变量;readiness 覆盖 DB/磁盘/上传/FFmpeg;自动 smoke 只读并验证 CORS allow/deny"} {"id":"rel:user_owns_pets","type":"Relation","subject":"entity:user","predicate":"OWNS","object":"entity:pet","cardinality":"1—N","evidence":"anchored","doc":"Pet.owner_user_id → User.id","domain":"user"} {"id":"rel:pet_has_appointments","type":"Relation","subject":"entity:pet","predicate":"HAS","object":"entity:appointment","cardinality":"1—N","evidence":"anchored","doc":"Appointment.pet_id(可空)","domain":"pet"} diff --git a/ontology/rules.md b/ontology/rules.md index 82f488b..fd50fca 100644 --- a/ontology/rules.md +++ b/ontology/rules.md @@ -6,11 +6,12 @@ ### rule:BR-AUTH-001 -**HMAC session token + 活跃账号范围鉴权**:protected `/api/**` 要求 `Authorization: Bearer `;token 为 HMAC-SHA256、默认 7 天有效期。签名通过后仍须读取活跃 User,确认账号未删除且 `role/storeId` 与 token 快照一致;boss/staff 还须确认所属 Store 仍为有效门店。删员工、停用门店或权限范围变化会立即令旧 token 失效。public allowlist 见 `AuthInterceptor.PUBLIC_PATTERNS`。 +**HMAC session token + 活跃账号范围鉴权**:protected `/api/**` 要求 `Authorization: Bearer `;token 为 HMAC-SHA256、默认 7 天有效期,`exp` 必须是未来 Unix 秒时间戳,缺失、非数字、非正或到期边界均 fail-closed。TTL 非正时服务拒绝构造,production 只允许 300~2592000 秒。签名通过后仍须读取活跃 User,确认账号未删除且 `role/storeId` 与 token 快照一致;boss/staff 还须确认所属 Store 仍为有效门店。删员工、停用门店或权限范围变化会立即令旧 token 失效。public allowlist 见 `AuthInterceptor.PUBLIC_PATTERNS`。 - **适用动作**:所有 protected 接口 - **代码**:`backend/src/main/java/com/petstore/auth/AuthInterceptor.java`、`SessionTokenService.java`、`CurrentUserContext.java` - **测试**:`SessionTokenServiceTest`、`AuthInterceptorActiveUserTest` +- **文档**:`docs/架构决策-SessionToken严格过期边界-2026-08-02.md` - **证据**:`anchored` ### rule:BR-AUTH-002 @@ -371,14 +372,14 @@ ### rule:BR-CONFIG-002 -**production profile**:`--spring.profiles.active=production` 时 `ddl-auto=validate`(不改表)、`show-sql=false`、`SMS_UNIVERSAL_CODE` 默认空(关闭万能验证码 `rule:BR-AUTH-002`),并跳过开发期默认服务初始化。最终配置不满足强密钥、显式生产 HTTPS 域名、绝对上传目录和显式 CORS 源时启动失败。 +**production profile**:`--spring.profiles.active=production` 时 `ddl-auto=validate`(不改表)、`show-sql=false`、`SMS_UNIVERSAL_CODE` 默认空(关闭万能验证码 `rule:BR-AUTH-002`),并跳过开发期默认服务初始化。最终配置不满足强密钥、300~2592000 秒 session TTL、显式生产 HTTPS 域名、绝对上传目录和显式 CORS 源时启动失败。 **CORS 单一策略**:全局 `CorsFilter` 是唯一跨域策略,白名单只来自 `CORS_ALLOWED_ORIGINS`;Controller 禁止局部 `@CrossOrigin`,防止配置合并或 Filter 变更后意外 fail-open。生产 smoke 必须同时验证一个白名单 Origin 精确回显、一个非白名单 Origin 返回 403。 - **适用动作**:—(全局约束) - **代码**:`backend/src/main/resources/application.yml`(multi-document production profile)、`ProductionConfigurationValidator.java`、`CorsConfig.java`、`PetstoreApplication.java` - **测试**:`ProductionConfigurationValidatorTest`、`CorsBoundaryContractTest` -- **文档**:`backend/README.md production profile` +- **文档**:`backend/README.md production profile`、`docs/架构决策-SessionToken严格过期边界-2026-08-02.md` - **证据**:`anchored`(真实生产值与启动证据仍属发布门禁) ### rule:BR-CONFIG-003 diff --git a/架构决策-SessionToken严格过期边界-2026-08-02.md b/架构决策-SessionToken严格过期边界-2026-08-02.md new file mode 100644 index 0000000..0215096 --- /dev/null +++ b/架构决策-SessionToken严格过期边界-2026-08-02.md @@ -0,0 +1,22 @@ +# 架构决策:Session Token 严格过期边界 + +> 日期:2026-08-02 +> 决策:`exp` 必填且必须在未来;非法 TTL 和非法过期载荷全部 fail-closed +> 本体:`rule:BR-AUTH-001`、`rule:BR-CONFIG-002` + +## 背景 + +Petstore 使用 HMAC-SHA256 session token。签发端始终写入 `exp`,但旧校验逻辑把缺失、无法解析或非正的 `exp` 转为 `0`,随后仅在 `exp > 0` 时检查过期。这意味着一枚签名正确但没有合法过期时间的载荷可能成为不过期会话。服务构造时还会把非正 TTL 静默回退为 7 天,使错误配置不易被发现。 + +## 决策 + +1. `verify` 要求 `exp` 可解析为 Unix 秒且严格大于当前时间;缺失、非数字、非正、过去时间以及等于当前秒全部拒绝。 +2. `SessionTokenService` 收到非正 TTL 时直接拒绝构造,不做默认回退;未显式配置仍由配置层使用 604800 秒默认值。 +3. production profile 与 `release-preflight.sh` 只允许 300~2592000 秒(5 分钟~30 天),兼顾短时误配防护和试点登录体验。 +4. 自动化测试使用真实 HMAC 构造签名正确的反例,证明拒绝原因来自过期契约,而不是签名失败。 + +## 兼容与边界 + +- 正常由现有签发端产生的 token 均包含合法 `exp`,无需数据库或客户端迁移。 +- 修改 `PETSTORE_SESSION_SECRET` 仍会使旧 token 立即失效;生产轮换必须安排重新登录提示。 +- 当前会话仍是无服务端状态的 HMAC token;员工删除、角色/门店变化由每次请求的活跃账号校验立即失效。独立 logout 吊销表仍不在本期试点范围。 diff --git a/生产发布与回滚Runbook-2026-08-01.md b/生产发布与回滚Runbook-2026-08-01.md index b7be5ab..d9ddbc7 100644 --- a/生产发布与回滚Runbook-2026-08-01.md +++ b/生产发布与回滚Runbook-2026-08-01.md @@ -15,6 +15,7 @@ 5. Petstore 使用独立域名、Nginx `server` 块、systemd unit 和发布目录。不得覆盖、重启或复用同机 Gitea/GitLab 的配置与服务。 6. 任何命令输出都不得包含数据库密码、微信 AppSecret、session secret、完整报告 token、手机号或私密媒体 URL。 7. CORS 只由后端全局 `CorsFilter` 控制;Controller 不得声明局部 `@CrossOrigin`,生产 smoke 必须同时验证白名单 Origin 与拒绝分支。 +8. Session token 必须携带未来 `exp`;`PETSTORE_SESSION_TTL_SECONDS` 仅允许 300~2592000 秒,非法值不得静默回退。 ## 2. 本次发布任务卡 @@ -53,6 +54,7 @@ - readiness 非 `UP`,上传目录/FFmpeg/数据库任一依赖失败; - 发现凭据、完整 token、手机号或私密媒体地址进入日志/工单/截图; - 任一 Controller 出现局部 `@CrossOrigin`,或非白名单 Origin 未返回 403; +- `PETSTORE_SESSION_TTL_SECONDS` 非整数、短于 5 分钟或长于 30 天; - 无明确值班人、回滚执行人或生产变更授权。 ## 4. 发布前冻结 @@ -133,7 +135,7 @@ systemd-run --wait --pipe --collect \ ./deploy/release-preflight.sh ``` -通过条件:静态配置检查通过、JPA `validate` 通过、31 项数据不变量全部为 0,并以退出码 0 结束。预检不会执行迁移或修复数据。 +通过条件:静态配置检查(含 session TTL)通过、JPA `validate` 通过、31 项数据不变量全部为 0,并以退出码 0 结束。预检不会执行迁移或修复数据。 ## 6. 构建发布产物