Laravel 8生成AWS S3预签名PUT URL返回令牌格式错误/无效问题排查
Laravel 8生成S3私有存储桶预签名Put URL时令牌格式错误问题
问题背景
我在Laravel v8环境中生成AWS S3私有存储桶的预签名上传URL,API部署在已配置全存储桶访问角色的EC2实例上。登录EC2执行aws s3 ls能正常显示关联存储桶,说明角色配置有效;且API通过Storage类直接上传文件到S3也能正常工作。但调用生成的预签名URL时,返回错误:The provided token is malformed or otherwise invalid。
当前实现代码
预签名URL生成函数
public function getPresignedPutUrl(string $diskName, string $bucketName, string $filePath) { $client = Storage::disk($diskName)->getDriver()->getAdapter()->getClient(); $expiry = '+10 minutes'; $contentType = 'video/' . explode('.', $filePath)[1]; $cmd = $client->getCommand('PutObject', [ 'Bucket' => $bucketName, 'Key' => $filePath, 'Expires' => 300, 'ContentType' => $contentType, ]); $request = $client->createPresignedRequest($cmd, $expiry); return $request->getUri(); }
S3磁盘配置
env('PRIVATE_DISK_NAME') => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET_PRIVATE'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), ],
注:出于安全考虑,AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY环境变量留空,凭证由EC2实例元数据提供。
可能原因分析
- 双重过期时间配置冲突:代码同时在
PutObject命令中设置Expires(300秒),又在createPresignedRequest中传入+10 minutes,AWS SDK处理时可能出现逻辑冲突,导致签名生成异常。 - 凭证链获取逻辑异常:虽然直接上传能正常使用EC2实例元数据,但预签名请求的客户端初始化可能未正确触发实例元数据的临时凭证获取。当.env中密钥为空但配置项仍存在时,SDK可能尝试用空值生成签名,导致令牌无效。
- Content-Type解析容错缺失:如果文件名无后缀或后缀解析失败,会生成无效的Content-Type(如
video/),虽然不直接导致令牌错误,但可能触发S3附加校验逻辑,间接引发报错。
修复方案
1. 移除重复的过期时间配置
只在createPresignedRequest中设置过期时间,删除PutObject命令内的Expires参数,避免冲突:
public function getPresignedPutUrl(string $diskName, string $bucketName, string $filePath) { $client = Storage::disk($diskName)->getDriver()->getAdapter()->getClient(); $expiry = '+10 minutes'; // 增加后缀容错处理,避免无后缀时生成无效Content-Type $fileExt = explode('.', $filePath)[1] ?? 'mp4'; $contentType = 'video/' . $fileExt; $cmd = $client->getCommand('PutObject', [ 'Bucket' => $bucketName, 'Key' => $filePath, 'ContentType' => $contentType, ]); $request = $client->createPresignedRequest($cmd, $expiry); return $request->getUri(); }
2. 明确配置凭证获取逻辑
在磁盘配置中显式指定凭证提供者,确保SDK优先使用EC2实例元数据:
env('PRIVATE_DISK_NAME') => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET_PRIVATE'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), 'use_credentials' => true, 'credential_provider' => 'instance', // 强制使用EC2实例元数据凭证 ],
3. 验证凭证获取状态(测试用)
在生成URL前临时打印客户端凭证信息,确认是否获取到有效的EC2临时凭证:
$credentials = $client->getCredentials()->wait(); dd($credentials); // 查看是否包含有效的AccessKeyId、SecretKey和Token
内容的提问来源于stack exchange,提问作者DGregory
相关产品推荐
相关产品推荐

