首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >官网表单的附件怎么传才不丢、不越权:临时凭证直传、回调验签与孤儿文件清理

官网表单的附件怎么传才不丢、不越权:临时凭证直传、回调验签与孤儿文件清理

原创
作者头像
用户5598620
发布于 2026-09-18 09:00:27
发布于 2026-09-18 09:00:27
1640
举报

导读:官网的招聘投递、合作留言、售后工单几乎都要传附件(简历、截图、合同)。常见做法是把文件 POST 到业务服务器再转存对象存储,结果大文件占满带宽、请求超时丢件,甚至把永久密钥放进前端造成越权。本文给出一套可直接落地的闭环:前端拿临时凭证直传对象存储、服务端回调验签落库、定时清理孤儿文件,附件与业务单据强绑定,不丢、不重、不被越权访问。

一、附件为什么不该经过业务服务器中转

很多官网表单第一版这样写:前端把文件和文字字段一起 multipart/form-data POST 到后端,后端收完整文件再上传对象存储。小文件没问题,简历动辄十几 MB、截图几张就上百 MB 时会暴露三个问题:

  • 请求体在后端内存或临时盘里完整落地,并发几个大文件就占满带宽和磁盘;
  • 上传时间长,网关超时后前端以为失败,用户重复提交,后端出现重复附件;
  • 为了让前端能直传而把永久 SecretKey 写进页面,等于公开了整个存储桶的读写权限。

直传把"传文件"这件事交给对象存储的就近接入点,后端只负责签发一张"只能往指定目录写、几分钟后过期"的临时凭证,自己不碰文件流。

二、怎么给前端一张只能传附件的临时凭证

临时凭证要遵循最小权限:只允许对"本次表单会话专属目录"执行写入,不给读、不列桶、不能碰其他路径。后端在用户进入表单页时先创建一个上传会话,返回会话号与对象键前缀,再据此签发凭证。

代码语言:python
复制
import time, uuid

def issue_upload_credential(form_id, user_ip):
    session_id = uuid.uuid4().hex
    # 对象键按业务隔离:附件类型/表单号/会话号/文件名
    prefix = f"form-attach/{form_id}/{session_id}/"
    policy = {
        "version": "2.0",
        "statement": [{
            "effect": "allow",
            "action": ["cos:PostObject", "cos:PutObject", "cos:InitiateMultipartUpload"],
            "resource": [f"qcs::cos:*:*:bucket-name/{prefix}*"],
            "condition": {
                "numeric_less_than": {"cos:content-length": 20 * 1024 * 1024},
                "date_less_than": {"cos:expire-time":
                                   time.strftime("%Y-%m-%dT%H:%M:%SZ",
                                                 time.gmtime(time.time() + 600))}
            }
        }]
    }
    save_session(session_id, form_id, prefix, user_ip, ttl=600)
    cred = sts_client.get_credential_of_policy(policy)
    return {"session_id": session_id, "prefix": prefix, "credential": cred}

要点有三:资源路径锁到 session_id 这一级,A 会话拿不到 B 会话的目录;用条件把单文件大小和有效期钉死;凭证有效期 5–10 分钟即可,传完就作废,不必为了"省事"发一小时。

三、前端直传后,怎么确认文件真的传成功了

前端不能凭"上传进度走到 100%"就告诉后端附件已就绪——弱网下可能传了一半断流。可靠做法是让对象存储在写入完成后回调业务后端,后端验明回调真身、校验对象键归属本次会话,再把附件登记为"已上传、待绑定"。

代码语言:python
复制
import hmac, hashlib, json

def upload_callback(request):
    # 1) 用回调密钥校验签名,拒绝伪造回调
    raw = request.body
    sign = request.headers.get("Authorization", "")
    expect = hmac.new(CALLBACK_SECRET, raw, hashlib.sha1).hexdigest()
    if not hmac.compare_digest("sha1=" + expect, sign):
        return 401, {"code": "bad_sign"}
    data = json.loads(raw)
    key, size = data["key"], int(data.get("size", 0))
    # 2) 对象键必须落在已登记的会话前缀下,防止窜改 key 绑定别人的文件
    sess = get_session(data.get("session_id"))
    if not sess or not key.startswith(sess["prefix"]):
        return 403, {"code": "key_mismatch"}
    # 3) 幂等登记:同一对象键重复回调只落一条
    upsert_attachment(key=key, form_id=sess["form_id"],
                      session_id=sess["session_id"], size=size,
                      status="uploaded")
    return 200, {"code": "ok"}

前端提交表单时只回传 session_id 和已确认的对象键列表,后端按会话把附件从"已上传"改成"已绑定单据"。这样即使前端重复点提交,附件与单据的绑定也是幂等的。

四、传了一半放弃的孤儿文件怎么清理

直传必然产生孤儿文件:用户选了文件传到一半关页面、回调丢失、或提交表单时没勾选这些附件。它们不会自己消失,会一直占用存储。需要两道清理:

  • 对象存储自身的分片上传 Abort 机制:设置"分片上传 N 天后自动中止",回收没合并的碎片;
  • 业务侧定时任务:扫描超过会话有效期仍停留在 uploaded、没有绑定到任何单据的对象登记,删除对象并写审计日志。
代码语言:sql
复制
-- 附件登记表:对象键唯一,保证回调与清理都幂等
CREATE TABLE form_attachment (
  id           BIGINT PRIMARY KEY AUTO_INCREMENT,
  form_id      VARCHAR(32)  NOT NULL,
  session_id   VARCHAR(64)  NOT NULL,
  object_key   VARCHAR(255) NOT NULL,
  size_bytes   BIGINT       NOT NULL DEFAULT 0,
  status       VARCHAR(16)  NOT NULL DEFAULT 'uploaded', -- uploaded/bound/deleted
  created_at   DATETIME     NOT NULL,
  bound_at     DATETIME     NULL,
  UNIQUE KEY uk_object_key (object_key),
  KEY idx_status_created (status, created_at)
);
代码语言:python
复制
def cleanup_orphan_objects(now):
    # 会话有效期 10 分钟,宽限到 1 小时仍未绑定即判定为孤儿
    rows = query("SELECT * FROM form_attachment "
                 "WHERE status='uploaded' AND created_at < %s", now - 3600)
    for r in rows:
        delete_object(r["object_key"])          # 删除前对象键仍受会话前缀约束
        execute("UPDATE form_attachment SET status='deleted' WHERE id=%s", r["id"])

删除走"先标记、再删对象、回写状态",任务中途失败重跑也不会重复计费或漏删。

踩坑清单

  • 把永久密钥放进前端:页面源码即可读取,等于交出整桶权限;一律改后端签发临时凭证。
  • 临时权限给成"整桶可写":最小权限要精确到会话前缀,并限制动作、大小、时效。
  • 用前端回调通知后端"传完了":可被伪造,应以对象存储服务端回调并验签为准。
  • 不校验对象键归属:攻击者把 key 改成他人文件路径即可冒名绑定,回调里必须校验前缀。
  • 只靠前端删文件:放弃上传的碎片和孤儿对象要靠生命周期规则加服务端定时任务兜底。
  • 附件与单据同步写入:应先传后绑、按 session_id 幂等绑定,避免重复提交产生重复附件。

工程落地建议

官网这类低频、文件不大的对外表单,直传方案用对象存储的标准临时凭证与上传回调即可,不必自建文件服务;会话前缀、回调验签、附件登记表、孤儿清理四件事建议作为统一的"附件中台"沉淀,招聘、留言、工单、报备等所有带附件的表单共用一套,避免每个表单各写一套密钥和回调。私密度高的附件在绑定后把对象权限切为私有,按单据归属做带时效的签名 URL 下载。

上线复盘清单

  • 前端不出现任何长期密钥,只有短时临时凭证
  • 临时策略精确到会话前缀,且限制动作 / 大小 / 有效期
  • 对象存储回调在服务端验签,并校验对象键归属会话
  • 附件按对象键幂等登记、按 session_id 幂等绑定
  • 配置分片自动中止与孤儿对象定时清理,删除动作可重入
  • 私密附件改为私有读,下载走时效签名链接

常见问题

Q:官网表单传附件,怎么保证用户传了一半失败不会产生脏文件?

A:用对象存储分片直传并配置"未完成分片自动中止",业务侧再把超过会话有效期仍未绑定单据的对象登记判定为孤儿,定时删除并回写状态,两层兜底即可。

Q:临时凭证直传会不会被人拿去传非法文件?

A:不会,只要把临时策略的资源限制在本次会话专属前缀、动作限制为写入、并用条件钉死文件大小和有效期,凭证几分钟后即失效,无法越权访问其他路径。

Q:怎么确认附件确实和这条留言或投递对应上了?

A:对象存储写入后服务端回调验签,校验对象键属于该会话再登记为已上传,表单提交时按会话号幂等绑定到单据,重复提交也只绑一次。

结语

以上为附件直传的一套工程化闭环实践,具体凭证时效、大小阈值与清理周期请按自身业务和对象存储厂商的实时文档调整。核心思路只有一句:后端发"最小权限、短时有效"的凭证,文件走近路,真实性和归属交给服务端回调与幂等登记兜底。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、附件为什么不该经过业务服务器中转
  • 二、怎么给前端一张只能传附件的临时凭证
  • 三、前端直传后,怎么确认文件真的传成功了
  • 四、传了一半放弃的孤儿文件怎么清理
  • 踩坑清单
  • 工程落地建议
  • 上线复盘清单
  • 常见问题
    • Q:官网表单传附件,怎么保证用户传了一半失败不会产生脏文件?
    • Q:临时凭证直传会不会被人拿去传非法文件?
    • Q:怎么确认附件确实和这条留言或投递对应上了?
  • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档