如何使用AWS Web API和Lambda对无服务器Web请求进行身份验证?

问题描述:

背景信息很少,

我建立了一个交互式网站,用户可以在其中将图像上传到S3.我构建了它,以便使用签名的请求(python django backend)将图像从浏览器直接上传到AWS S3.

I have built an interactive website where users can upload images to S3. I built it so the image upload goes right from the browser to AWS S3 using a signed request ( python django backend ).

现在的问题是,用户希望能够旋转图像.同样,我希望进行此设置,以便用户的请求直接来自浏览器.我构建了一个AWS Lambda函数并将其附加到一个Web api,它将接受POST请求.我一直在测试,终于使它工作了.该函数接受2个输入keyrotate_direction,它们作为POST变量传递到Web api.它们进入event变量中的python函数.这是简单的Lambda函数:

Now the issue is, the users wish to be able to rotate the image. I Similarly I would like this set up so the user's request goes straight from the browser. I built an AWS Lambda function and attached it to a web api, which will accept POST requests. I have been testing and I finally got it working. The function takes 2 inputs, key, and rotate_direction, which are passed as POST variables to the web api. They come into the python function in the event variable. Here is the simple Lambda function:

from __future__ import print_function
import boto3
import os
import sys
import uuid
from PIL import Image

s3_client = boto3.client('s3')

def rotate_image(image_path, upload_path, rotate_direction):
    with Image.open(image_path) as image:
        if rotate_direction == "right":
            image.rotate(-90).save(upload_path)
        else:
            image.rotate(90).save(upload_path)

def handler(event, context):
    bucket = 'the-s3-bucket-name'
    key = event['key']
    rotate_direction = event['rotate_direction']
    download_path = '/tmp/{}{}'.format(uuid.uuid4(), key)
    upload_path = '/tmp/rotated_small-{}'.format(key)


    s3_client.download_file(bucket, key, download_path)
    rotate_image(download_path, upload_path, rotate_direction)
    s3_client.delete_object(Bucket=bucket, Key=key)
    s3_client.upload_file(upload_path, bucket, key)

    return { 'message':'rotated' }

一切正常.因此,现在我的问题是如何对该系统实施某种身份验证?有关每个图像的所有权详细信息位于django Web服务器上.虽然所有图片都被视为公开"图片,但我希望强制每个图片的所有者只能旋转自己的图片.

Everything is working. So now my issue is how to enforce some kind of authentication for this system? The ownership details about each image reside on the django web server. While all the images are considered "public", I wish to enforce that only the owner of each image is allowed to rotate their own images.

通过这个项目,我一直在通过直接从浏览器发出内容请求进入新的领域.我可以理解如何仅通过从Web服务器发出POST请求来控制访问,在Web服务器中可以验证图像的所有权.该请求是否仍可能来自浏览器?

With this project I have been venturing into new territory by making content requests right from the browser. I could understand how I could control access by only making the POST requests from the web server, where I could validate the ownership of the images. Would it still be possible having the request come from the browser?

TL; DR解决方案:创建一个Cognito身份池,分配策略用户只能上传以其ID为前缀的文件.

TL;DR Solution: create a Cognito Identity Pool, assign policy users can only upload files prefixed by their Identity ID.

如果我正确理解了您的问题,则希望设置一种方式来存储在S3上的图像可以公开查看,但只能由上传它的用户修改 >.实际上,您可以在浏览器中验证文件所有权,旋转图像并将旋转的图像上传到S3,而无需通过Lambda函数.

If I understand your question correctly, you want to setup a way for an image stored on S3 to be viewable by public, yet only editable by the user who uploaded it. Actually, you can verify file ownership, rotate the image and upload the rotated image to S3 all in the browser without going through a Lambda function.

第1步:创建Cognito用户池以创建用户目录.如果您已经具有用户登录/注册身份验证系统,则可以跳过此步骤.

Step 1: Create a Cognito User Pool to create a user directory. If you already have a user login/sign up authentication system, you could skip this step.

第2步:创建一个Cognito身份池以启用联盟身份,以便您的用户可以从身份池中获得临时的AWS凭证,并使用它将文件上传到S3,而无需通过服务器/lambda.

Step 2: Create a Cognito Identify Pool to enable federated identity, so your users can get a temporary AWS credential from the Identity Pool, and use it to upload files to S3 without going through your server/lambda.

步骤3:创建Cognito身份池时,您可以定义有关允许用户访问哪些S3资源的策略.这是示例政策

Step 3: When creating the Cognito Identity Pool, you can define a policy on what S3 resources a user is allowed to access. Here is a sample policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "mobileanalytics:PutEvents",
        "cognito-sync:*",
        "cognito-identity:*"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::YOUR_S3_UPLOADS_BUCKET_NAME/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::YOUR_S3_UPLOADS_BUCKET_NAME/${cognito-identity.amazonaws.com:sub}*"
      ]
    }
  ]
}

请注意,第二个块将"S3:GetObject"分配给S3存储桶中的所有文件;第三个块将"S3:PutObject"分配给仅以用户的Cognito身份ID为前缀的文件.

Note the second block assigns "S3:GetObject" to all files in your S3 bucket; and the third block assigns "S3:PutObject" to ONLY FILES prefixed with the user's Cognito Identity ID.

第4步:在前端JS中,从Cognito身份池中获取临时证书

Step 4: In frontend JS, get a temporary credential from Cognito Identity Pool

export function getAwsCredentials(userToken) {
  const authenticator = `cognito-idp.${config.cognito.REGION}.amazonaws.com/${config.cognito.USER_POOL_ID}`;

  AWS.config.update({ region: config.cognito.REGION });

  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: config.cognito.IDENTITY_POOL_ID,
    Logins: {
      [authenticator]: userToken
    }
  });

  return new Promise((resolve, reject) => (
    AWS.config.credentials.get((err) => {
      if (err) {
        reject(err);
        return;
      }

      resolve();
    })
  ));
}

第5步:使用凭据将文件上传到S3,并在文件名前添加用户的Cognito身份ID.

Step 5: Upload files to S3 with the credential, prefix the file name with the user's Cognito Identity ID.

export async function s3Upload(file, userToken) {
  await getAwsCredentials(userToken);

  const s3 = new AWS.S3({
    params: {
      Bucket: config.s3.BUCKET,
    }
  });
  const filename = `${AWS.config.credentials.identityId}-${Date.now()}-${file.name}`;

  return new Promise((resolve, reject) => (
    s3.putObject({
      Key: filename,
      Body: file,
      ContentType: file.type,
      ACL: 'public-read',
    },
    (error, result) => {
      if (error) {
        reject(error);
        return;
      }

      resolve(`${config.s3.DOMAIN}/${config.s3.BUCKET}/${filename}`);
    })
  ));
}