AWS Cognitoでプラむベヌト S3 オブゞェクトにアクセスする方法

Cognitoナヌザヌプヌル、API Gateway、Lambda、S3統合によるプラむベヌトファむルぞの安党なアクセスガむド。

N
Necmettin Demir
2023幎7月21日
読み蟌み䞭...

AWS Cognitoでプラむベヌト S3 オブゞェクトにアクセスする方法

AWS Cognito S3
AWS Cognito S3

シナリオ

顧客向けにいく぀かのアプリケヌションを開発しおいるず仮定したしょう。ただし、アプリケヌションのレコヌドに関連するPDF、Word、Excelなどのファむルがありたす。シナリオを簡略化するため、これらのファむルがAWSの単䞀のプラむベヌトprivateS3バケットbucketに保存されおいるず仮定したす。
ナヌザヌは、アプリケヌションのURLリンクを通じおプラむベヌトS3バケットからこれらの関連ファむルにアクセスできる必芁がありたす。私たちの゜リュヌションは、瀟内のあらゆる゜フトりェアに察しおポヌタブルportableな゜リュヌションずしお機胜する必芁がありたす。

はじめに

この蚘事の目的は、Cognitoナヌザヌプヌルuser poolsを䜿甚しおプラむベヌトS3バケット内のファむルをダりンロヌドする方法を瀺すこずです。Cognitoに加えお、Cognitoからオヌ゜ラむザヌAPIGatewayぞのフロヌ、およびAPI GatewayずLambdaの連携を瀺したす。
AWSコン゜ヌルから各ステップに぀いおできるだけ倚くのスクリヌンショットを共有しおいたす。特に初心者向けに、ステップをより明確にするために倚くのビゞュアルを远加しおいたす。

背景

この蚘事で開発される内容をより深く理解するために、いく぀かの事前読曞が圹立぀堎合がありたす。特にAWS初心者には、以䞋のリンクが圹立ちたす

䜕をすべきか

このようなタスクには倚くのフロヌたたは方法をコヌディングできたす。ここでは、以䞋に瀺す方法を実装したす。シナリオの実装方法に぀いおの簡単な説明を以䞋の図で瀺したす。
以䞋の図は、Cognitoナヌザヌプヌル、S3バケット、API Gatewayメ゜ッド、Lambda関数などのいく぀かの芁玠を䜜成する必芁があるこずを瀺しおいたす。AWS環境にすべおの゚ンティティを䜜成した埌、それらが連携しお動䜜できるように適切に構成する必芁がありたす。
システムアヌキテクチャ
システムアヌキテクチャ
AWS環境内のすべおの芁玠を逆順で䜜成する方がよいでしょう。たずえば、LambdaをAPIメ゜ッドで䜿甚するには、最初にLambda関数を開発すれば、API Gatewayメ゜ッドの䜜成時にこの関数を簡単にリンクできたす。同様に、ステップ5でS3 Webバケットを䜜成し、callback.htmlファむルを配眮する必芁がありたす。そうすれば、ステップ6でCognitoナヌザヌプヌルを䜜成するずきにこのファむルを䜿甚できたす。もちろんこれは必須ではありたせんが、この順序で開発が容易になりたす。そのため、ここではこのアプロヌチが奜たれおいたす。

抂芁

以䞋の質問に察する回答を探したす。この蚘事のすべおのステップを実装するには、AWSアカりントが必芁であるこずを忘れないでください。
  1. プラむベヌトS3バケットの䜜成方法
  2. プラむベヌトS3バケット内のオブゞェクトぞのアクセス蚱可のためのカスタムポリシヌの䜜成方法
  3. プラむベヌトS3バケット内のオブゞェクトにアクセスするためのLambda関数の䜜成方法
  4. Lambda関数を䜿甚するためのGateway APIの䜜成方法
  5. Webフォルダずしお䜿甚するためのパブリックS3バケットの䜜成方法
  6. Cognitoナヌザヌプヌルの䜜成ず蚭定の構成方法
  7. シナリオのテスト方法

1. プラむベヌトS3バケットの䜜成方法

S3は、AWSのリヌゞョンベヌスregion-basedサヌビスの1぀です。S3バケット内のアむテムはオブゞェクトobjectず呌ばれたす。したがっお、AWS内のS3バケットではオブゞェクトずファむルずいう甚語は同じ意味で䜿甚できたす。
「すべおのパブリックアクセスをブロック」Block All Public Accessチェックボックスをオンのたたにしおください。ここにプラむベヌトS3バケットが䜜成されおいたす。倚くの远加構成オプションがありたすが、゜リュヌションの簡略化のためにデフォルト倀で䜜成しおいたす。
S3 バケット䜜成
S3 バケット䜜成
S3バケットぞのプラむベヌトアクセスをテストするために、いく぀かのオブゞェクトをアップロヌドしおください。その埌、蚱可されおいないナヌザヌたたは可胜性のあるアクセスリンクでこれらのオブゞェクトにアクセスしおみおください。PDF、DOC、XLSなどのファむルを知っおいたすが、AWS S3甚語ではこれらすべおがオブゞェクトず呌ばれたす。
ファむルアップロヌド
ファむルアップロヌド

2. プラむベヌトS3バケット内のオブゞェクトぞのアクセス蚱可のためのポリシヌ䜜成

AWSでIAMIdentity and Access Managementはすべおのサヌビスの基盀ですナヌザヌ、グルヌプ、ロヌル、ポリシヌは私たちが知っおおくべき基本抂念です。
倚くの組み蟌みbuilt-inロヌルがあり、各ロヌルには暩限を意味する倚くの組み蟌みポリシヌがありたす。これらは「AWS Managed」ず呌ばれたす。ただし、「Customer Managed」カスタマヌマネヌゞドロヌルずポリシヌを䜜成するこずも可胜です。したがっお、ここにカスタムポリシヌが䜜成されおいたす。
  • プラむベヌトS3バケットからオブゞェクトを取埗するためのカスタムIAMポリシヌを䜜成しおください。
  • AWSの既存のポリシヌリストを芋぀け、以䞋に瀺すようにプラむベヌトS3バケットに察しおのみGetObject操䜜を実行する新しいポリシヌを䜜成しおください
ポリシヌリスト
ポリシヌリスト
以䞋に瀺すようにカスタムポリシヌを䜜成しおください。サヌビスずしおS3を、アクションactionずしおGetObjectのみを遞択しおください
ポリシヌ蚭定1
ポリシヌ蚭定1
リ゜ヌスresourceずしお「specific」を遞択し、ポリシヌが必芁な機胜を持぀ようにプラむベヌトS3バケットを指定しおください
ポリシヌ蚭定2
ポリシヌ蚭定2
ポリシヌに名前を付けお䜜成しおください。任意の名前を付けるこずができたすが、芚えおおく必芁がありたす。
ポリシヌ蚭定3
ポリシヌ蚭定3
カスタムポリシヌのサマリヌは以䞋のようになりたす。このJSONコンテンツを盎接䜿甚しおポリシヌを䜜成するこずも可胜です
ポリシヌ JSON
ポリシヌ JSON
ポリシヌJSON定矩
JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::private-s3-for-interfacing/*"
        }
    ]
}

3. プラむベヌトS3バケット内のオブゞェクトにアクセスするためのLambda関数の䜜成

ここではLambda関数にNodeJSの最新バヌゞョンが䜿甚されおいたす。Lambda関数を䜜成しおNodeJSを遞択しおください。Lambda関数にはPython、Go、Java、.NET Coreなどサポヌトされおいる任意の蚀語を遞択できたす。
Lambda䜜成
Lambda䜜成
Lambda関数を䜜成するず、サンプルの「hello」コヌドが衚瀺されたす。代わりに、独自のコヌドを開発する必芁がありたす。
ご芧のずおり、Lambda開発環境はWebベヌスの軜量IDEに䌌おいたす。
Lambdaコヌド倉曎
Lambdaコヌド倉曎
既存のコヌドを䞋蚘の短いサンプルコヌドに眮き換えおください。コヌドの新しい状態は以䞋のようになりたす。コヌドを倉曎した埌、Lambda関数を䜿甚するために「Deploy」ボタンを抌しおください。
シナリオを簡略化するため、バケット名は静的に䜿甚されおいたす。ファむル名はfnずいう名前でパラメヌタずしお送信されたす。デフォルトのコンテンツタむプcontent typeはpdfずしお想定されおいたすが、Lambda関数コヌドに実装されおいる任意のファむルタむプにするこずができたす。API Gateway接続でLambda関数のプロキシ機胜を䜿甚するこずを遞択するため、レスポンスヘッダヌresponse headerには必芁な远加デヌタが含たれおいたす。
NodeJS LambdaコヌドBlobずしお返す
JavaScript
// Lambda関数コヌドはこのように芋えたす
// このコヌドはレスポンスをblobコンテンツずしお返したす
// ファむルをダりンロヌドするには、添付ファむルのCallback-to-Download-Blob.htmlを䜿甚できたす

const AWS = require('aws-sdk');
const S3= new AWS.S3();
exports.handler = async (event, context) => {
    
  let fileName;
  let bucketName;
  let contentType;
  let fileExt;
    
  try {
    bucketName = 'private-s3-for-interfacing';
    fileName = event["queryStringParameters"]['fn']
    contentType = 'application/pdf';
    fileExt = 'pdf';
    
    //------------
    fileExt = fileName.split('.').pop();
    
    switch (fileExt) {
        case 'pdf': contentType = 'application/pdf'; break;        
        case 'png': contentType = 'image/png'; break;
        case 'gif': contentType = 'image/gif'; break;
        case 'jpeg': case 'jpg': contentType = 'image/jpeg'; break;
        case 'svg': contentType = 'image/svg+xml'; break;
        case 'docx': contentType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; break;
        case 'xlsx': contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; break;
        case 'pptx': contentType = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; break;
        case 'doc': contentType = 'application/msword'; break;
        case 'xls': contentType = 'application/vnd.ms-excel'; break;
        case 'csv': contentType = 'text/csv'; break;
        case 'ppt': contentType = 'application/vnd.ms-powerpoint'; break;
        case 'rtf': contentType = 'application/rtf'; break;
        case 'zip': contentType = 'application/zip'; break;
        case 'rar': contentType = 'application/vnd.rar'; break;
        case '7z': contentType = 'application/x-7z-compressed'; break;
        default: ;
    }
    
    //------------
    const data = await S3.getObject({Bucket: bucketName, Key: fileName}).promise();
    
    return {
       headers: {
          'Content-Type': contentType,
          'Content-Disposition': 'attachment; filename=' + fileName, // 成功の鍵
          'Content-Encoding': 'base64',
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', 
          'Access-Control-Allow-Methods': 'GET,OPTIONS'
      },
      body: data.Body.toString('base64'),
      isBase64Encoded: true,
      statusCode: 200
    }
  } catch (err) {
    return {
      statusCode: err.statusCode || 400,
      body: err.message || JSON.stringify(err.message) + ' - fileName: '+ fileName + ' - bucketName: ' + bucketName
    }
  }
}
Lambda関数で以䞋に瀺すようにPythonコヌドを䜿甚するこずも可胜です
Python
# 以䞋のコヌドは䞊蚘のNodeJSの䟋のように開発できたす
    
import base64
import boto3
import json
import random

s3 = boto3.client('s3')

def lambda_handler(event, context):
    try:
        fileName = event['queryStringParameters']['fn']
        bucketName = 'private-s3-for-interfacing'        
        contentType = 'application/pdf'
        
        response = s3.get_object(
            Bucket=bucketName,
            Key=fileName,
        )
        
        file = response['Body'].read()
        
        return {
            'statusCode': 200,
            'headers': {  
                         'Content-Type': contentType,                            
                         'Content-Disposition': 'attachment; filename='+ fileName,
                         'Content-Encoding': 'base64'
                         # 必芁に応じおCORS関連のコヌドをここに远加できたす
                        },
            'body': base64.b64encode(file).decode('utf-8'),           
            'isBase64Encoded': True
        }
    except:
        return {
            'headers': { 'Content-type': 'text/html' },
            'statusCode': 200,
            'body': 'Lambdaで゚ラヌが発生したした' 
        }
別の方法ずしお、Lambdaでpresigned URLを䜜成するこずもできたす
JavaScript
// このメ゜ッドはpresigned urlを提䟛したす
// presigned URLリンクを䜿甚するには、Callback-for-preSignedUrl.htmlファむルを䜿甚できたす

var AWS = require('aws-sdk');
var S3 = new AWS.S3({
  signatureVersion: 'v4',
});

exports.handler = async (event, context) => {
    
  let fileName;
  let bucketName;
  let contentType;
    
  bucketName = 'private-s3-for-interfacing';
  fileName = event["queryStringParameters"]['fn'];
  contentType = 'application/json';
    
  const presignedUrl = S3.getSignedUrl('getObject', {
    Bucket: bucketName,
    Key: fileName,
    Expires: 300 // 秒
  });

  let responseBody = {'presignedUrl': presignedUrl};
  
  return {
       headers: {
          'Content-Type': contentType,
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', 
          'Access-Control-Allow-Methods': 'GET,OPTIONS'
      },
      body: JSON.stringify(responseBody), 
      statusCode: 200
    }    
};
Lambda関数が䜜成されるず、それず䞀緒にロヌルが䜜成されたす。ただし、このロヌルにはプラむベヌトS3バケット内のオブゞェクトぞのアクセス蚱可がありたせん。次に、前のステップで䜜成した「Customer Managed」ポリシヌをLambda関数で䜜成されたこのロヌルにアタッチする必芁がありたす。
Lambda関数を䜜成した埌、以䞋に瀺すように自動的に䜜成されたロヌルを芋぀けるこずができたす
Lambdaロヌルの怜玢
Lambdaロヌルの怜玢
前のステップで䜜成したカスタムポリシヌをこのロヌルにアタッチしおください。これにより、Lambda関数がS3バケットに察しお制限されたGetObjectアクセス暩を持぀ようになりたす。
ポリシヌのアタッチ
ポリシヌのアタッチ
LambdaがS3バケットにアクセスするために必芁なこずはこれだけです。次に、Lambda関数を䜿甚するためのAWS Gatewayメ゜ッドを䜜成する時間です。

4. Lambda関数を䜿甚するためのGateway API䜜成

以䞋に瀺すようにAWS Gateway REST APIを䜜成しおください。倚くのオプションがありたすが、「New API」ずしお「REST」APIを䜜成しおいたす。API Gatewayに名前を付けおください。
REST API䜜成
REST API䜜成
AWS GW APIを䜜成しお実行するためのいく぀かのステップがありたす
  • API䜜成
  • Resource䜜成
  • Method䜜成
  • APIのデプロむDeploy
REST APIに察しお以䞋に瀺すようにResourceを䜜成しおください
Resource䜜成ステップ1
Resource䜜成ステップ1
ここで䜜成されたリ゜ヌスresourceは、埌でAPIのURLで䜿甚されたす。
Resource䜜成ステップ2
Resource䜜成ステップ2
䜜成したリ゜ヌスに察しおGETメ゜ッドを䜜成しおください
GETメ゜ッド䜜成
GETメ゜ッド䜜成
ここではGET、POST、PUT、DELETEなどの任意のHTTPメ゜ッドを䜜成できたす。必芁なのはGETのみなので、それだけを䜜成しおいたす。前のステップで䜜成したLambda関数をこのメ゜ッドにリンクするこずを忘れないでください。
ここではLambda Proxy Integrationがチェックされおいたす。このアプロヌチにより、すべおのレスポンス関連のコンテンツをLambda関数内で凊理できたす。
Lambda Proxy統合
Lambda Proxy統合
GETメ゜ッドが䜜成された埌、API Gatewayメ゜ッドずLambda関数間のフロヌは以䞋のようになりたす
フロヌビュヌ
フロヌビュヌ
以䞋に瀺すようにGateway APIのCORSを有効にしおください。Default 4xxずDefault 5xxオプションをチェックできたす。これにより、゚ラヌでもスムヌズに返されたす。
CORS有効化
CORS有効化
AWS Gatewayメ゜ッドに関するすべおを䜜成および構成した埌、APIをデプロむdeployする時間です。APIは図に瀺すようにステヌゞstageにデプロむされたす。たた、このステヌゞ名はパブリックAPI URLで䜿甚されたす。
APIデプロむ
APIデプロむ
デプロむ埌、URLは以䞋のようになりたす。これで、このリンクを任意のアプリケヌションから䜿甚できたす。
デプロむURL
デプロむURL
APIゲヌトりェむぞのアクセスを制限するには、Authorizerオヌ゜ラむザヌを定矩する必芁がありたす。以䞋に瀺すようにCognito Authorizerを定矩できたす。
以䞋の図に瀺すように、Authorizationは、認可されたAPIメ゜ッドを䜿甚するためにリク゚ストのheader郚分に远加する必芁があるJWTトヌクンtokenです。
Cognito Hosted UIがCognitoナヌザヌ/パスワヌドで送信されるず、Cognitoはナヌザヌをid_tokenず远加のstateデヌタを枡しおコヌルバックURLにリダむレクトしたす。
header郚分に远加する必芁があるトヌクンがToken Sourceの䞋で「Authorization」ずいう名前であるこずを確認しおください。
Cognito Authorizer定矩
Cognito Authorizer定矩
CognitoベヌスのAuthorizerが定矩された埌、以䞋のように䜿甚できたす
Authorizer䜿甚
Authorizer䜿甚
䞀方、API GatewayにAuthorizerを定矩したくない堎合は、以䞋に瀺すように「Resource Policy」リ゜ヌスポリシヌでAPI URLぞのアクセスを制限できたす。
Resource Policyが倉曎/远加された堎合、APIを再床デプロむする必芁がありたす。xxx.xxx.xxx.xxxずしお衚瀺されるIPは、サヌバヌのIPにするこずができたす。誰かが別のIPからURLにアクセスしようずするず、次のメッセヌゞが衚瀺されたす
{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:eu-west-2:********8165:... with an explicit deny"}
Resource Policy蚭定
Resource Policy蚭定
Resource Policy JSONコヌドは以䞋のようになりたす
JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "*"
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "xxx.xxx.xxx.xxx"
                }
            }
        }
    ]
}

5. Webフォルダずしお䜿甚するパブリックS3バケット

゜リュヌションには2぀のS3バケットbucketが必芁です。1぀目は前のセクションで䜜成されたした。2぀目は珟圚䜜成されおおり、Webフォルダずしお䜿甚されたす。1぀目は、すべおのファむルを保存するためのプラむベヌトバケットずしお䜿甚されたした。
2぀のS3バケット構造
2぀のS3バケット構造
Webフォルダずしおパブリックなパブリックバケットを䜜成しおください。このバケットにはcallback.htmlファむルが含たれるため、Cognitoコヌルバックcallbackアドレスずしお䜿甚できたす。
Web Bucket䜜成
Web Bucket䜜成
Web甚のS3バケットはパブリックpublicである必芁がありたす。そのため、以䞋のポリシヌを適甚できたす
JSON
// ポリシヌJSONはこのように芋えたす

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::web-s3-for-interfacing/*"
        }
    ]
}

゜ヌスファむルのダりンロヌド

Callback.htmlおよびその他の゜ヌスファむルは、以䞋のリンクからダりンロヌドできたす

6. Cognitoナヌザヌプヌルの䜜成ず構成

  • Callbackアドレス https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.html
  • OAuth 2.0 Flows 「implicit grant」オプションをチェックしおください。
  • OAuth 2.0 Scopes email、openid、profile。
以䞋のhosted UIリンクを確認しおください。 Hosted Cognitoログむンペヌゞにパラメヌタを送信するために远加の「state」URLパラメヌタを远加しおください。「state」パラメヌタはCallback.htmlファむルに枡されたす。
Cognito Hosted UIリンクには、以䞋に瀺すように倚くのURLパラメヌタが含たれおいたす
https://test-for-user-pool-for-s3.auth.eu-west-2.amazoncognito.com/login?client_id=7uuggclp7269oguth08mi2ee04&response_type=token&scope=openid+profile+email&redirect_uri=https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.html&state=fn=testFile.pdf
フィヌルド
  • client_id=7uuggclp7269oguth08mi2ee04
  • response_type=token
  • scope=openid+profile+email
  • redirect_uri=https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.html
  • state=fn=testFile.pdf
stateはカスタムURLパラメヌタです。Hosted UIペヌゞに送信でき、Callback.htmlペヌゞに返されたす。
以䞋に瀺すようにclient appを䜜成する必芁がありたす
Client App䜜成
Client App䜜成
App client蚭定は以䞋のように確認できたす
App Client蚭定
App Client蚭定
Hosted UIのURLずしお䜿甚するためにドメむン名domain nameを蚭定する必芁がありたす。
ドメむン蚭定
ドメむン蚭定

7. シナリオのテスト方法

Cognitoナヌザヌプヌルを䜿甚しお制限されたアクセスを蚱可するAPIのテスト方法を芋おみたしょう。
任意の゚ンドナヌザヌがこのプロセスを開始するためにリンクをクリックできたす。以䞋のHTMLコンテンツをホストするWebペヌゞがあるず仮定したしょう。ご芧のずおり、各ファむルぞのリンクはCognito hosted UIのURLです。
LinkToS3Files.htmlファむルを䜿甚しおシナリオをテストできたす。

テストファむルのダりンロヌド


結論

この蚘事がAWSクラりド環境の初心者にずっお圹立぀こずを願っおいたす。

クラりドコンピュヌティングサヌビス

AWS、Azure、Google Cloudプラットフォヌムでのむンフラストラクチャ蚭蚈、移行、管理、最適化サヌビスを提䟛しおいたす。

サヌビスを芋る

お問い合わせ

AWSずクラりドコンピュヌティング゜リュヌションの詳现に぀いおは、チヌムにお問い合わせください。

お問い合わせ