关于从iPhone应用程序和S3上传照片的架构和设计问题

时间:2010-12-19 02:27:32

标签: iphone ios mobile ios4 amazon-s3

我想允许iPhone应用的用户上传照片并使用Amazon S3。我有两种方法可以解决这个问题:

  1. 从iPhone上传到我的服务器,然后将其代理到Amazon S3。
  2. 从iPhone直接上传到S3
  3. 对于选项1,安全性很简单。我永远不必告诉iPhone我的S3秘密。缺点是所有内容都通过我们的服务器代理上传,这有点挫败了进入S3的目的。

    对于选项2,理论上它更好,但问题是如何在不给我秘密的情况下将iPhone(或不同平台上的任何移动应用程序)写入我的S3存储桶?另外,我不确定这是否是一个好的设计,因为流程将是:iphone上传到S3,获取URL,然后告诉服务器URL是什么,以便它可以将它添加到我们的数据库以引用未来。但是,由于通信被分成两条腿(iphone-> S3 vs iPhone-> My-Server),因此它将其作为非原子操作而变得脆弱。

    我发现了一些使用Browser-based Uploads using POST引用的旧信息,但不确定这是否仍然是推荐的方法。我希望有一个更好的解决方案,我们可以使用REST API而不是依赖POST。我也看到了AWS iOS Beta SDK,但他们的文档没有多大帮助,我发现Amazon article同样无益,因为它会警告你要做什么,但是没有告诉你另一种方法:

      

    移动AWS软件开发工具包对API进行签名   发送到Amazon Web Services的请求   (AWS)为了验证   制作的AWS账户的身份   请求。否则,是恶意的   开发人员可以轻松提出请求   到另一个开发人员的基础设施。   请求使用AWS签署   访问密钥ID和秘密访问密钥   AWS提供的。秘密通道   密钥类似于密码,它   保守秘密非常重要。

         

    提示:您可以查看所有AWS   安全凭证,包括Access   密钥ID和秘密访问密钥,在   AWS网站在   http://aws.amazon.com/security-credentials

         

    在源代码中嵌入凭据   对软件有问题,包括   移动应用,因为恶意   用户可以解压缩软件或   查看源代码以检索   秘密访问密钥。

    有没有人对最佳建筑设计和流程有任何建议?

    更新:我越深入研究,似乎有很多人建议将HTTP POST方法与json策略文件一起使用,如下所述:http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/index.html?UsingHTTPPOST.html

    有了这个,流程就像(1)iPhone向我的服务器发出请求,请求策略文件(2)服务器生成json策略文件并返回给客户端(3)iPhone执行HTTP POST照片+ json S3的政策。我讨厌我以一种显而易见的方式使用HTTP POST,但它看起来更好,因为它根本不需要我的服务器来存储照片。

5 个答案:

答案 0 :(得分:10)

我之前讨论过这个问题on the AWS forums。正如我所说,从移动设备访问AWS的正确解决方案是使用AWS Identity and Access Management服务为每个用户生成临时的,有限权限的访问密钥。该服务非常棒,但它目前仍处于测试阶段,而且还不是移动SDK的一部分。我有一种感觉,一旦这个东西发布好了,你会立即在移动SDK上看到它。

在此之前,generate presigned URLs为您的用户提供服务,或通过您自己的服务器代理,就像其他人所建议的那样。预先签名的URL将允许您的用户临时GET或PUT到您的一个存储桶中的S3对象,而不实际拥有您的凭据(它们被哈希到签名中)。您可以阅读详细信息here

编辑:我已经使用IAM的预览测试版实现了针对此问题的正确解决方案。它是GitHub上的available,你可以read about it here

答案 1 :(得分:5)

您可以使用REST API直接从iPhone上传到S3,并让服务器负责生成需要密钥的Authorization标头值的一部分。这样,您就不会冒险将访问密钥暴露给拥有越狱iPhone的任何人,而您不会将文件上载的负担放在服务器上。可以在"Example Object PUT" of "Signing and Authenticating REST Requests"下找到要求的确切详细信息。我强烈建议阅读该文档,然后再继续。

以下代码(用Python编写)生成从{3}}标头值中派生自S3秘密访问密钥的部分。您应该分别用下面的Authorization_S3_SECRET替换您自己的秘密访问密钥和bucket name in virtual host form

_S3_BUCKET_NAME

使用文件名import base64 from datetime import datetime import hmac import sha # Replace these values. _S3_SECRET = "my-s3-secret" _S3_BUCKET_NAME = "my-bucket-name" def get_upload_header_values(content_type, filename): now = datetime.utcnow() date_string = now.strftime("%a, %d %b %Y %H:%M:%S +0000") full_pathname = '/%s/%s' % (_S3_BUCKET_NAME, filename) string_to_sign = "PUT\n\n%s\n%s\n%s" % ( content_type, date_string, full_pathname) h = hmac.new(_S3_SECRET, string_to_sign, sha) auth_string = base64.encodestring(h.digest()).strip() return (date_string, auth_string) 和内容类型foo.txt调用此选项会产生:

text/plain

请注意,如果您运行此代码,则返回的时间将不同,因此编码的HMAC摘要将不同。

现在,iPhone客户端只需使用返回的日期和HMAC摘要向S3发出PUT请求。假设

  • 服务器在名为>>> get_upload_header_values('text/plain', 'foo.txt') ('Wed, 06 Feb 2013 00:57:45 +0000', 'EUSj3g70aEsEqSyPT/GojZmY8eI=')
  • 的某个JSON对象中返回上面的date_stringauth_string
  • 您的S3访问密钥(不是您的秘密,仅在服务器上)名为serverJson
  • 您的S3存储桶名称(上面设置为kS3AccessKey)名为my-bucket-name
  • 文件内容在名为kS3BucketName
  • NSData对象中进行编组
  • 发送到服务器的文件名是名为data
  • 的字符串
  • 发送到服务器的内容类型是名为filename
  • 的字符串

然后,您可以执行以下操作来创建contentType

NSURLRequest

接下来,您可以发出请求。如果您使用了优秀的AFNetworking library,则可以使用XMLDocumentRequestOperationWithRequest:success:failure:NSString *serverDate = [serverJson objectForKey:@"date"] NSString *serverHmacDigest = [serverJson objectForKey:@"hmacDigest"] // Create the headers. NSMutableDictionary *headers = [[NSMutableDictionary alloc] init]; [headers setObject:contentType forKey:@"Content-Type"]; NSString *host = [NSString stringWithFormat:@"%@.s3.amazonaws.com", kS3BucketName] [headers setObject:host forKey:@"Host"]; [headers setObject:serverDate forKey:@"Date"]; NSString *authorization = [NSString stringWithFormat:@"AWS %@:%@", kS3AccessKey, serverHmacDigest]; [headers setObject:authorization forKey:@"Authorization"]; // Create the request. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; [request setAllHTTPHeaderFields:headers]; [request setHTTPBody:data]; [request setHTTPMethod:@"PUT"]; NSString *postUrl = [NSString stringWithFormat:@"http://%@.s3.amazonaws.com/%@", kS3BucketName, filename]; [request setURL:[NSURL URLWithString:postUrl]]; 包裹在request对象中,然后调用其AFXMLRequestOperation方法。完成后不要忘记发布startheaders

请注意,客户端从服务器获取request标头的值。这是因为正如亚马逊在"Time Stamp Requirement"下所描述的那样:

  

“对于经过身份验证的请求,必须使用有效的时间戳(使用HTTP日期标头或x-amz-date备用方案)。此外,经过身份验证的请求中包含的客户端时间戳必须在亚马逊的15分钟内收到请求时的S3系统时间。如果没有,请求将因RequestTimeTooSkewed错误状态代码而失败。“

因此,不依赖于具有正确时间的客户端以使请求成功,而是依赖服务器,该服务器应该使用NTP(以及Date之类的守护进程。)

答案 2 :(得分:4)

上传到您的服务器,然后发布到S3。从架构的角度来看,您需要从服务器执行此操作。在数据传输过程中可能出现许多问题,您可以在服务器上更好地处理,如果您想要存储有关您发送给S3的图像的任何数据,您可能会进行服务器端呼叫。

另外,您的秘密访问密钥存储在更安全的环境中。你自己的。

如果您担心可扩展性并且您将要进行大量的S3传输,我会考虑托管您的服务器和EC2实例。在两者之间传输数据是免费的(假设您将数据存储在以下数据中心)。

  

对于在同一地区内的Amazon EC2和Amazon S3之间传输的数据,或者对于Amazon EC2 Northern Virginia Region和Amazon S3 US Standard Region之间传输的数据,不收取数据传输费用。“Amazon Simple Storage Service (Amazon S3)

关于使用EC2的利弊,SO Amazon - EC2 cost?(示例)上有很多帖子。

答案 3 :(得分:0)

我很困惑。亚马逊为什么会出现ios sdk将数据上传到s3然后告诉我们不要使用它(在源代码中嵌入凭据对软件有问题,包括移动应用程序,因为恶意用户可以对软件进行反编译或查看源代码以检索秘密访问密钥)???

答案 4 :(得分:0)

他们可能提供了sdk,目的是申请可能允许对个人s3帐户进行身份验证?例如,一个应用程序,允许用户将文件存储在他们自己(用户)的存储桶而不是提供商?我觉得将密钥与应用程序合并并分发它存在安全漏洞。任何人都可以(错误)使用它们一旦钥匙被揭示(当你放弃它时它永远不会安全)。另一方面,保留服务器保留的功能将使您的密钥对用户透明,不是吗?