Laravel 保护 Amazon s3 存储桶文件

Laravel secure Amazon s3 bucket files

我正在使用 Amazon s3,但我遇到了两个问题

1.I 当我提交 form.I 时无法直接将文件上传到亚马逊服务器意味着我必须将图像上传到我的 PHP 服务器上的 upload folder 并且从我必须检索它们并将它们上传到 s3 server。有没有办法在我们点击提交的时候直接上传图片到s3

2.If 我在 s3 put object 中传递 'public' 然后我可以访问或查看文件,但如果我做到 public 每个人都可以查看文件。但我需要保护所有文件并仅供经过身份验证的用户查看。谁能建议我如何解决这个问题?

try {           
    $s3 = \Storage::disk('s3');
    $s3->put($strFileName, file_get_contents($img_path.$strFileName), 'public');
} catch (Aws\Exception\S3Exception $e) {
    echo "There was an error uploading the file.\n"+$e;
}

在提问之前,我已经阅读了很多来自 Whosebug 的答案,但它并没有帮助我解决我的问题。谢谢。

第一个问题,直接上传图片到AWS S3即可。

$s3 = \Storage::disk('s3')->getDriver();
$s3->put($filePath, file_get_contents($file), array('ContentDisposition' => 'attachment; filename=' . $file_name . '', 'ACL' => 'public-read'));

您应该指定您的文件路径和您从表单中获取的文件。

1.有没有点击提交直接上传图片的方法

如何:

您需要使用 JavaScript(使用 AJAX)分两部分执行此操作;

a) 当用户点击 "submit" 时,您会捕获此事件,首先上传文件(参见 http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-examples.html 示例),然后 b) 然后通过AJAX提交表单并处理响应。

但是:

这允许用户上传任何内容,但可能会导致问题。有提示(在示例下方)创建经过验证的 URL 15 分钟(可变)但是如果用户花费的时间超过 15 分钟,或者尝试在 15 分钟内上传 100 个文件,或者上传图像文件以外的其他内容,会发生什么情况,或格式错误的图像文件等

进入您的服务器并验证它们是您需要的图像和 type/size 然后从服务器上传会更安全。

当然,如果这是一个简单的管理工具,并且您正在控制谁可以访问代码,那么就去做吧——希望您只会上传您期望的内容。

2。我需要保护所有文件并仅供经过身份验证的用户查看

By "authenticated user":如果您的意思是 "the user that uploaded the image",那么单独的 s3 不提供该功能,但 CloudFront 提供。您可以发布预授权 URL 或签名 cookie:http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-choosing-signed-urls-cookies.html

如果 "authenticated user" 表示上传文件的人,那么根据文档,如果不访问底层客户端,这在 Laravel class 中是不可能的。默认情况下,publicprivate 是您唯一的可见性选项,它们被转换为 public-read,但您需要 authenticated-readbucket-owner-read 或其他罐装补助金之一(参考:http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl)如果 authenticated-read 或其他固定 ACL 授权没有提供您需要的权限配置文件,您可以创建自己的配置文件(详情请参见同一页面)。

解决方法是抓取底层client,然后直接做put-object。 (然后,如果你走得那么远,你还不如放弃 Laravel 库并引入 s3 并自己完成所有工作 - 然后你就可以完全控制一切)。

$client = $disk->getDriver()->getAdapter()->getClient();

$client->putObject([
    'Bucket' => $bucket,
    'Key'    => $fileName,
    'Body'   => $fileBody,
    'ACL'    => 'authenticated-read'  /* Or which ACL suits */
]);

我最近解决了这个问题。首先是的,你可以直接上传到 s3 这是我用来获取一些信息的东西:http://docs.aws.amazon.com/AmazonS3/latest/dev/HTTPPOSTExamples.html

首先,您需要创建一个策略和签名服务器端以添加到您的 html 表单以上传文件。

$policy = base64_encode(json_encode([
            "expiration" => "2100-01-01T00:00:00Z",
            "conditions" => [
                ["bucket"=> "bucketname"],
                ["starts-with", '$key', "foldername"],
                ["acl" => "public-read"],
                ["starts-with", '$Content-Type', "image/"],
                ["success_action_status" => '201'],
            ]
        ]));
$signature = base64_encode(hash_hmac('sha1',$policy,getenv('S3_SECRET_KEY'),true));

现在在我的表单的前端,我不使用提交按钮,您可以使用提交按钮,但您需要捕获提交并阻止表单在上传完成之前实际提交。

当我们点击保存时,它会生成一个md5(使用npm安装)文件名,这样文件名就不会被随机猜测了,然后它会使用ajax将文件上传到S3。完成后,它将文件数据和返回的 aws 数据放入隐藏的输入中并提交表单。它应该看起来像这样:

<form action="/post/url" method="POST" id="form">
    <input type="text" name="other_field" />
    <input type="file" class="form-control" id="image_uploader" name="file" accept="image/*" />
    <input type="hidden" id="hidden_medias" name="medias" value="[]" />
</form>
<input type="button" value="save" id="save" />
<script>
$(document).ready(function(){
    $('#save').click(function(){
            uploadImage(function () {
                $('#form').submit();
            });
    });
});
var uploadImage = function(callback) {
    var file = $('#image_uploader')[0].files[0];
    if(file !== undefined) {
        var data = new FormData();
        var filename = md5(file.name + Math.floor(Date.now() / 1000));
        var filenamePieces = file.name.split('.');
        var extension = filenamePieces[filenamePieces.length - 1];
        data.append('acl',"public-read");
        data.append('policy',"{!! $policy !!}");
        data.append('signature',"{!! $signature !!}");
        data.append('Content-type',"image/");
        data.append('success_action_status',"201");
        data.append('AWSAccessKeyId',"{!! getenv('S3_KEY_ID') !!}");
        data.append('key',filename + '.' + extension);
        data.append('file', file);

        var fileData = {
            type: file.type,
            name: file.name,
            size: file.size
        };

        $.ajax({
            url: 'https://{bucket_name}.s3.amazonaws.com/',
            type: 'POST',
            data: data,
            processData: false,
            contentType: false,

            success: function (awsData) {
                var xmlData = new XMLSerializer().serializeToString(awsData);
                var currentImages = JSON.parse($('#hidden_medias').val());
                currentImages.push({
                    awsData: xmlData,
                    fileData: fileData
                });
                $('#hidden_medias').val(JSON.stringify(currentImages));
                callback();
            },
            error: function (errorData) {
                console.log(errorData);
            }
        });
    }
};
</script>

侦听提交的控制器然后从该输入字段解析 JSON 并创建媒体实例(我创建的模型)并存储 awsDatafileData对于每个图像。

然后像这样将 html 图像标签指向 s3 文件:

<img src="https://{bucketname}.s3.amazonaws.com/filename.jpg" />

我是这样做的:

<img src="/medias/{id}" />

然后路由就可以通过正常的auth中间件和所有你需要在Laravel中做的事情了。最后,该路由指向执行此操作的控制器:

public function getResponse($id)
{
    $media = Media::find($id);
    return (new Response('',301,['Location' => $media->info['aws']['Location']]));
}

所以它所做的只是使用 301 重定向并将 header 位置设置为实际的 aws 文件。由于我们在将文件上传到 aws 时会生成一个 md5 文件名,因此每个文件名都是一个 md5,因此人们无法在存储桶中随机搜索 aws 文件。

你的问题的解决方案,我也在使用,但没有 laravel。

1.要将任何文件直接上传到特定文件夹中的 Amazon AWS S3 Bucket,您可以这样做。

HTML

<form action="upload.php" enctype="multipart/form-data">
    <input type="file" name="file" id="file" />
    <button type="submit">Upload file</button>
</form>

PHP - upload.php

包括aws php sdk

require "vendor/autoload.php";

初始化 S3 客户端

$credentials = new Aws\Credentials\Credentials(
    '<AWS ACCESS KEY>', 
    '<AWS ACCESS SECRET>'
);

$s3Client = Aws\S3\S3Client::factory(
    [
        'credentials' => credentials
        'region' => 'us-east-1',
        'version' => 'latest'
    ]
);

创建文件上传实体

$uploadEntity = array(
    'Bucket' => <S3 Bucket Name>,
    'Key'    => '<Upload_Folder_If_Any>/<FileName>',
    'Body'   => fopen($_FILES['file']['tmp_name'], 'r+'),
    //'ContentDisposition' => 'attachment; filename="<FileName>"' <-- If need to allow downloading
);

上传到 s3 存储桶

try {
    // $result will be Aws\Result object
    $result = $s3Client->putObject($uploadEntity);  
} catch (Aws\S3\Exception\S3Exception $exception) {
    // S3 Exception
}

2。现在仅将上传的文件提供给经过身份验证的用户

首先,您需要为 s3 存储桶创建私有存储桶策略。

存储桶策略 - 要生成存储桶策略,您可以使用 Policy Generator. Using this you can generate policy like this. Copied from Improve.dk website

{
  "Id": "Policy1319566876317",
  "Statement": [
    {
      "Sid": "Stmt1319566860498",
      "Action": [
        "s3:GetObject"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::<BucketName>/*",
      "Principal": {
        "CanonicalUser": [
          "<CannoicalUserId_of_OAI>"
        ]
      }
    }
  ]
}

其次,您需要设置 s3 Bucket 的云端私有 Web 分发。只有这样做,您才能通过 aws signed url 或 signed cookie

向经过身份验证的用户提供您的内容

第三,要生成签名的 url,您将需要 pem 文件,您只能从 aws 控制台获得该文件。

生成签名url

$cdnName = '<AWS CLOUDFRONT WEB DISTRIBUTION CDN>';
$assetName = '<UPLOAD_FOLDER_IF_ANY>/<FILENAME>';
$expiry = time() + 300;     // 5 mins expiry time, ie. the signed url will be valid only for 5 mins

$cloudfront = CloudFrontClient::factory(array(
    'credentials' => $credentials,
    'region' => 'us-east-1',
    'version' => 'latest'
));

// Use this for creating signed url
$signedUrl = $cloudFront->getSignedUrl([
    'url'         => $cdnName . '/' . $assetName,
    'expires'     => $expiry,
    'private_key' => '/path/to/your/cloudfront-private-key.pem',
    'key_pair_id' => '<cloudfront key pair id>'
]);

// Use this for signed cookie    
$signedCookie = $cloudFront->getSignedCookie([
    'url'         => $cdnName . '/' . $assetName,
    'expires'     => $expiry,
    'private_key' => '/path/to/your/cloudfront-private-key.pem',
    'key_pair_id' => '<cloudfront key pair id>'
]);