如何验证用户通过Presigned URL上传至S3 Bucket的文件?(Serverless框架)
如何在S3预签名URL上传时验证数据内容
针对你遇到的预签名URL无上传限制问题,提供以下几种落地解决方案:
一、预签名阶段强制绑定验证参数(客户端上传时直接拦截)
生成预签名URL时,将需要验证的元数据(如文件类型、大小范围)加入签名参数,客户端上传时必须严格匹配这些参数,否则S3会直接拒绝请求。
修改后的Lambda代码:
module.exports.uploadLarge = async (event) => { console.log({ event }) try { const body = JSON.parse(event.body); const action = body.action; const type = body.type; const key = body.key; // 第一步:提前校验客户端传入的文件类型是否在允许列表内 const allowedContentTypes = ['image/jpeg', 'image/png', 'application/pdf']; if (!allowedContentTypes.includes(type)) { return { statusCode: 400, headers: { 'Access-Control-Allow-Origin': '*' }, body: JSON.stringify({ error: '不允许的文件类型' }) }; } const params = { Bucket: BucketName, Key: key, Expires: 10000, } if (action === "putObject") { // 强制将ContentType加入签名参数,客户端必须携带一致的请求头 params.ContentType = type; // 可选:限制文件大小范围(0到50MB) params.ContentLengthRange = { Min: 0, Max: 1024 * 1024 * 50 }; } console.log({ params }) const u = S3.getSignedUrl(action, params); return { statusCode: 200, body: JSON.stringify({ u }), headers: { 'Access-Control-Allow-Origin': '*' } } } catch (err) { return { statusCode: 500, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }, body: JSON.stringify(err) } } }
效果说明:
- 客户端上传时必须使用预签名中指定的
Content-Type,否则返回403错误 - 如果设置了
ContentLengthRange,文件大小超出范围会直接被S3拦截
二、上传完成后触发Lambda异步验证文件内容
如果需要验证文件真实内容(比如是否是真图片,而非改后缀的文件),可以通过S3事件触发Lambda,在后台校验并清理非法文件。
1. Serverless配置片段(添加S3事件触发)
functions: uploadLarge: # 你的现有函数配置 events: - http: path: upload method: post cors: true validateUpload: handler: handlers.validateUpload.handler events: - s3: bucket: ${self:custom.BucketName} event: s3:ObjectCreated:* rules: - prefix: uploads/ # 只监听指定前缀的文件 existing: true # 若桶已存在需添加此配置 iamRoleStatements: - Effect: Allow Action: - s3:GetObject - s3:DeleteObject Resource: arn:aws:s3:::${self:custom.BucketName}/*
2. 验证Lambda代码示例
const AWS = require('aws-sdk'); const s3 = new AWS.S3(); const fileType = require('file-type'); // 需要安装该库:npm install file-type module.exports.handler = async (event) => { const record = event.Records[0]; const bucket = record.s3.bucket.name; const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' ')); try { // 获取文件元数据 const head = await s3.headObject({ Bucket: bucket, Key: key }).promise(); const declaredType = head.ContentType; // 下载文件前100字节检测真实类型 const fileBuffer = await s3.getObject({ Bucket: bucket, Key: key, Range: 'bytes=0-99' }).promise().then(res => res.Body); const detectedType = await fileType.fromBuffer(fileBuffer); // 校验真实类型与声明类型是否匹配 const typeMap = { 'image/jpeg': ['jpg', 'jpeg'], 'image/png': ['png'], 'application/pdf': ['pdf'] }; if (!typeMap[declaredType] || !typeMap[declaredType].includes(detectedType?.ext)) { // 删除非法文件 await s3.deleteObject({ Bucket: bucket, Key: key }).promise(); console.log(`已删除非法文件:${key},检测到真实类型:${detectedType?.ext}`); return { statusCode: 200, body: '非法文件已清理' }; } console.log(`文件验证通过:${key}`); return { statusCode: 200, body: '文件验证通过' }; } catch (err) { console.error('验证失败:', err); return { statusCode: 500, body: JSON.stringify(err) }; } };
三、S3桶策略直接限制上传条件
通过桶策略强制要求上传请求满足指定条件,作为前两种方案的补充。
示例桶策略:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::your-bucket/*", "Condition": { "StringEquals": { "Content-Type": ["image/jpeg", "image/png", "application/pdf"] }, "ContentLengthLessThan": 52428800 // 限制最大50MB } } ] }
方案选择建议
- 仅需限制文件类型、大小等元数据:用预签名绑定参数+桶策略组合,拦截成本最低
- 需要验证文件真实内容:必须配合上传后Lambda异步验证,因为S3本身不支持内容级校验
内容的提问来源于stack exchange,提问作者hydraCode
相关产品推荐
相关产品推荐

