【问题标题】:Laravel secure Amazon s3 bucket filesLaravel 保护 Amazon s3 存储桶文件
【发布时间】:2016-10-21 04:06:06
【问题描述】:

我使用的是 Amazon s3,但在这里我面临两个问题

1.当我提交表单时,我无法直接将文件上传到亚马逊服务器。我的意思是我必须将图像上传到我的 PHP 服务器上的upload folder,然后我必须从那里检索并将它们上传到s3 server .有没有办法在我们点击提交的时候直接上传图片到s3?

2.如果我在s3 put object 中传递'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;
}

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

【问题讨论】:

    标签: php laravel laravel-5 amazon-s3 laravel-4


    【解决方案1】:

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

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

    你应该指定你的文件路径和你从表单中得到的文件。

    【讨论】:

    • 感谢您的回答您知道如何访问私人图像或保护图像不被公开
    【解决方案2】:

    1.有没有办法在我们点击提交的时候直接上传图片

    是的

    如何:

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

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

    但是:

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

    拉入您的服务器并验证它们是图像以及您需要的类型/大小然后从服务器上传会更安全。

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

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

    通过“经过身份验证的用户”:如果您的意思是“上传图像的用户”,那么单独的 s3 不提供该功能,但 CloudFront 提供。您可以发布预先授权的 URL 或签名的 cookie:http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-choosing-signed-urls-cookies.html

    如果“经过身份验证的用户”是指上传文件的人,那么根据文档,如果没有访问底层客户端,这在 Laravel 类中是不可能的。默认情况下,public 和 private 是您唯一的可见性选项,它们被转换为 public-read,但您需要 authenticated-read、bucket-owner-read 或其他罐头授权之一(参考:http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl)如果 @ 987654330@ 或其他罐装 ACL 授权不会提供您需要的权限配置文件,您可以创建自己的(在同一页面上进一步详细说明)。

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

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

    【讨论】:

    • @Robbie。感谢您的详细回答。'ACL' => 'authenticated-read' 好的。我怎样才能找回它。因为我是亚马逊 s3 服务器的新手。再次感谢
    • 你需要一个编码示例来做什么?在 laravel 库之外做事的最佳参考是 AWS SDK for PHP(这里:docs.aws.amazon.com/aws-sdk-php/v3/api/…)
    【解决方案3】:

    我最近解决了这个问题。首先是的,您可以直接上传到 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 并创建一个 Media 实例(我创建的一个模型)并为每个图像存储 awsData 和 fileData。

    然后不是像这样将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 重定向并将标头位置设置为实际的 aws 文件。由于我们在将文件上传到 aws 时会生成一个 md5 文件名,因此每个文件名都是一个 md5,因此人们无法在存储桶中随机搜索 aws 文件。

    【讨论】:

    • @Pitchinnate。感谢您提供更多详细信息。我一定会尝试让您知道。非常感谢您花时间解释
    • @Pitchinnate ..对我来说,现在重要的是保护图像免受未经授权的人的侵害。你已经解释得很清楚了,我仍然对如何使用它感到困惑。如果你不介意可以解释一下吗进一步从 s3 安全地检索图像
    • 所以从技术上讲,这些图像并没有得到完全保护,但这使得试图获取它们的人很难。图像请求首先到达 Laravel,Laravel 能够验证用户是否已登录并有权查看图像(可以通过中间件完成)。如果用户确实有权限,它会向实际文件发送 301 和位置。
    • s3 上的文件在技术上是公开的,但由于它是一个 md5 文件名,人们不能只猜测文件名。他们甚至可以看到 md5 文件名和到 AWS 的路径的唯一方法是成为授权用户,查看图像,在浏览器的调试工具中打开网络选项卡并查看响应标头。
    • 我也有一篇关于此的博客文章,其中包含更多细节:natedenlinger.com/…
    【解决方案4】:

    我也在使用但没有 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 - 上传.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。使用它,您可以生成这样的策略。复制自Improve.dk website

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

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

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

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

    生成签名网址

    $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>'
    ]);
    

    【讨论】:

    • @Haridarshan。感谢您的回答稍后再试
    • @iCoders 将添加一些设置 s3 存储桶策略和私有网络分发所需的更多基本内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-21
    • 2018-04-12
    • 1970-01-01
    • 2016-08-25
    • 1970-01-01
    • 2020-01-15
    • 2021-12-02
    相关资源
    最近更新 更多