الحوسبة السحابية
13 دقيقة للقراءة

كيفية الوصول إلى كائنات S3 الخاصة باستخدام AWS Cognito

دليل للوصول الآمن إلى الملفات الخاصة باستخدام مجموعات مستخدمي Cognito و API Gateway و Lambda و S3.

ن
نجم الدين دمير
21 يوليو 2023
جاري التحميل...

كيفية الوصول إلى كائنات S3 الخاصة باستخدام AWS Cognito

AWS Cognito S3
AWS Cognito S3

السيناريو

لنفترض أنك تقوم بتطوير بعض التطبيقات لعميلك. ومع ذلك، هناك بعض الملفات مثل PDF و Word و Excel المتعلقة بالسجلات في التطبيقات. لتبسيط السيناريو، لنفترض أن هذه الملفات مخزنة في حاوية S3 خاصة (private) واحدة في AWS.
يحتاج المستخدمون إلى الوصول إلى هذه الملفات ذات الصلة من حاوية S3 الخاصة عبر رابط URL في التطبيقات. يجب أن يعمل حلنا كحل قابل للنقل (portable) لأي برنامج داخلي في الشركة.

المقدمة

الهدف من هذا المقال هو توضيح كيفية تنزيل الملفات من حاوية S3 الخاصة باستخدام مجموعات مستخدمي Cognito (user pools). بالإضافة إلى Cognito، يتم عرض التدفق من Cognito إلى API Gateway مع المُصدِّق (Authorizer) ثم تعاون API Gateway مع Lambda.
تم مشاركة أكبر عدد ممكن من لقطات الشاشة من وحدة تحكم AWS لكل خطوة. تمت إضافة العديد من الصور لتوضيح الخطوات بشكل أفضل خاصة للمبتدئين.

الخلفية

قد تكون بعض القراءات المسبقة مفيدة لفهم ما تم تطويره في هذا المقال بشكل أفضل. الروابط التالية ستكون مفيدة خاصة للمبتدئين في AWS:

ماذا يجب أن نفعل؟

يمكن برمجة العديد من التدفقات أو الأساليب لمثل هذه المهمة. هنا، سنطبق الطريقة الموضحة أدناه. يتم تقديم شرح موجز لكيفية تنفيذ السيناريو في الصورة التالية.
تُظهر الصورة التالية أنه يجب علينا إنشاء بعض العناصر مثل مجموعة مستخدمي Cognito وحاويات S3 وطرق API Gateway ودوال Lambda وما إلى ذلك. بعد إنشاء جميع العناصر في بيئة AWS، نحتاج إلى تكوينها بشكل مناسب حتى تتمكن جميعها من العمل معًا بشكل متكامل.
هندسة النظام
هندسة النظام
من الأفضل إنشاء جميع العناصر في بيئة AWS بترتيب عكسي. على سبيل المثال، لاستخدام Lambda مع طريقة API، إذا تم تطوير دالة Lambda أولاً، يمكن ربط هذه الدالة بسهولة عند إنشاء طريقة API Gateway. بالمثل، يجب علينا إنشاء حاوية S3 للويب في الخطوة 5 ووضع ملف callback.html فيها حتى نتمكن من استخدام هذا الملف عند إنشاء مجموعة مستخدمي Cognito في الخطوة 6. بالطبع هذا ليس إلزاميًا، لكن هذا الترتيب سيسهل التطوير. لذلك تم تفضيل هذا النهج هنا.

المخطط

سنبحث عن إجابات الأسئلة التالية. تذكر أنه يجب أن يكون لديك حساب AWS لتطبيق جميع الخطوات في هذا المقال.
  1. كيفية إنشاء حاوية S3 خاصة (Bucket)؟
  2. كيفية إنشاء سياسة مخصصة للوصول إلى الكائنات في حاوية S3 الخاصة؟
  3. كيفية إنشاء دالة Lambda للوصول إلى الكائنات في حاوية S3 الخاصة؟
  4. كيفية إنشاء Gateway API لاستخدام دالة Lambda؟
  5. كيفية إنشاء حاوية S3 عامة لاستخدامها كمجلد ويب؟
  6. كيفية إنشاء وتكوين مجموعة مستخدمي Cognito؟
  7. كيفية اختبار السيناريو؟

1. كيفية إنشاء حاوية S3 خاصة؟

S3 هي إحدى الخدمات المستندة إلى المنطقة (region-based) في AWS. تُسمى العناصر في حاويات S3 كائنات (object). لذلك، يمكن استخدام مصطلحي الكائن والملف بالتبادل لحاويات S3 في AWS.
احتفظ بخانة الاختيار "حظر جميع الوصول العام" (Block All Public Access) محددة. تم إنشاء حاوية S3 خاصة هنا. على الرغم من وجود العديد من خيارات التكوين الإضافية، نقوم بالإنشاء بالقيم الافتراضية لتبسيط الحل.
إنشاء حاوية S3
إنشاء حاوية S3
قم بتحميل بعض الكائنات إلى حاوية S3 لاختبار الوصول الخاص. لاحقًا، حاول الوصول إلى هذه الكائنات من مستخدمين غير مصرح لهم أو روابط وصول محتملة. على الرغم من أننا نعرف ملفات PDF و DOC و XLS وما إلى ذلك، إلا أن جميعها تُسمى كائنات في مصطلحات AWS S3.
تحميل الملفات
تحميل الملفات

2. إنشاء سياسة للوصول إلى الكائنات في حاوية S3 الخاصة

في AWS، IAM (إدارة الهوية والوصول) هي أساس جميع الخدمات! المستخدمون والمجموعات والأدوار والسياسات هي المفاهيم الأساسية التي يجب أن نكون على دراية بها.
هناك العديد من الأدوار المدمجة (built-in) ولكل دور العديد من السياسات المدمجة التي تعني الأذونات. تُسمى هذه "AWS Managed". ومع ذلك، من الممكن أيضًا إنشاء أدوار وسياسات "Customer Managed" (مُدارة بواسطة العميل). لذلك، تم إنشاء سياسة مخصصة هنا.
  • قم بإنشاء سياسة IAM مخصصة لسحب الكائنات من حاوية S3 الخاصة بك.
  • ابحث عن قائمة السياسات الموجودة في AWS وقم بإنشاء سياسة جديدة لتنفيذ عملية GetObject فقط من حاوية S3 الخاصة بك كما هو موضح أدناه:
قائمة السياسات
قائمة السياسات
قم بإنشاء سياسة مخصصة كما هو موضح أدناه. اختر S3 كخدمة و GetObject فقط كإجراء (action):
إعدادات السياسة 1
إعدادات السياسة 1
اختر "specific" كمورد (resource) وحدد حاوية 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. إنشاء دالة Lambda للوصول إلى الكائنات في حاوية S3 الخاصة

تم استخدام أحدث إصدار من NodeJS لدالة Lambda هنا. قم بإنشاء دالة Lambda واختر NodeJS. من الممكن اختيار أي لغة مدعومة مثل Python أو Go أو Java أو .NET Core لدالة Lambda.
إنشاء Lambda
إنشاء Lambda
عند إنشاء دالة Lambda، يتم عرض كود "hello" كمثال. نحتاج إلى تطوير كودنا الخاص بدلاً من ذلك.
كما ترى، تشبه بيئة تطوير Lambda IDE خفيفة الوزن قائمة على الويب.
تغيير كود Lambda
تغيير كود Lambda
استبدل الكود الموجود بالكود المثال القصير المعطى. سيبدو الكود الجديد كما يلي. بعد تغيير الكود، اضغط على زر "Deploy" لاستخدام دالة Lambda.
تم استخدام اسم الحاوية بشكل ثابت لتبسيط السيناريو. يتم إرسال اسم الملف كمعامل باسم fn. على الرغم من أن نوع المحتوى الافتراضي (content type) يُفترض أنه pdf، إلا أنه يمكن أن يكون أي نوع ملف مطبق في كود دالة Lambda. نظرًا لأننا سنفضل استخدام ميزة proxy لدالة Lambda في اتصال API Gateway، يحتوي رأس الاستجابة (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
    }
  }
}
من الممكن أيضًا استخدام كود Python في دالة Lambda كما هو موضح أدناه:
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': 'Error occurred in Lambda!' 
        }
طريقة أخرى هي إنشاء presigned URL مع Lambda:
JavaScript
// ستوفر هذه الطريقة presigned url
// يمكن استخدام ملف Callback-for-preSignedUrl.html لاستخدام رابط Presigned URL

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 على حق وصول GetObject مقيد على حاوية S3 الخاصة بك.
إرفاق السياسة
إرفاق السياسة
هذا كل ما يجب القيام به لوصول Lambda إلى حاوية S3 الخاصة بك. الآن، حان الوقت لإنشاء طريقة AWS Gateway لاستخدام دالة Lambda الخاصة بنا.

4. إنشاء Gateway API لاستخدام دالة Lambda

قم بإنشاء AWS Gateway REST API كما هو موضح أدناه. هناك العديد من الخيارات ولكننا ننشئ "REST" API كـ "New API". قم بتسمية API Gateway الخاص بك.
إنشاء REST API
إنشاء REST API
هناك بعض الخطوات لإنشاء وتشغيل AWS GW API:
  • إنشاء API
  • إنشاء Resource
  • إنشاء Method
  • نشر API (Deploy)
قم بإنشاء Resource لـ REST API الخاص بك كما هو موضح أدناه:
إنشاء Resource الخطوة 1
إنشاء Resource الخطوة 1
المورد (resource) الذي تم إنشاؤه هنا سيُستخدم لاحقًا في URL الخاص بـ API.
إنشاء Resource الخطوة 2
إنشاء Resource الخطوة 2
قم بإنشاء طريقة GET للمورد الذي أنشأته:
إنشاء GET Method
إنشاء GET Method
هنا يمكن إنشاء أي طريقة HTTP مثل GET أو POST أو PUT أو DELETE وما إلى ذلك. لاحتياجاتنا، نقوم بإنشاء GET فقط. لا تنس ربط دالة Lambda التي أنشأناها في الخطوات السابقة بهذه الطريقة.
تم تحديد Lambda Proxy Integration هنا. يتيح لنا هذا النهج معالجة جميع المحتويات المتعلقة بالاستجابة في دالة Lambda.
تكامل Lambda Proxy
تكامل Lambda Proxy
بعد إنشاء طريقة GET، سيبدو التدفق بين طريقة API Gateway ودالة Lambda كما يلي:
عرض التدفق
عرض التدفق
قم بتمكين CORS لـ Gateway API كما هو موضح أدناه. يمكن تحديد خياري Default 4xx و Default 5xx؛ وبالتالي يمكن إرجاع الأخطاء بسلاسة.
تمكين CORS
تمكين CORS
بعد إنشاء وتكوين كل شيء يتعلق بطريقة AWS Gateway، حان الوقت الآن لنشر (deploy) API. يتم نشر API إلى مرحلة (stage) كما هو موضح. كما سيُستخدم اسم هذه المرحلة في URL العام لـ API.
نشر API
نشر API
بعد النشر، سيبدو URL كما يلي. الآن من الممكن استخدام هذا الرابط من أي تطبيق.
URL النشر
URL النشر
لتقييد الوصول إلى API gateway، يجب تعريف Authorizer (مُصدِّق). يمكننا تعريف مُصدِّق Cognito كما هو موضح أدناه.
كما هو موضح في الصورة أدناه، Authorization هو رمز JWT (token) الذي يجب إضافته إلى قسم header في الطلب لاستخدام طريقة API المصرح بها.
عند إرسال Cognito Hosted UI باستخدام مستخدم/كلمة مرور Cognito، سيقوم Cognito بإعادة توجيه المستخدم إلى URL الـ callback مع تمرير id_token وبيانات state الإضافية.
لاحظ أن الرمز الذي نحتاج إلى إضافته إلى قسم header يُسمى "Authorization" ضمن Token Source.
تعريف Cognito Authorizer
تعريف Cognito Authorizer
بعد تعريف المُصدِّق المستند إلى Cognito، يمكن استخدامه كما يلي:
استخدام المُصدِّق
استخدام المُصدِّق
من ناحية أخرى، إذا كنت لا ترغب في تعريف Authorizer لـ API Gateway، يمكنك تقييد الوصول إلى URL الخاص بـ API باستخدام "Resource Policy" (سياسة المورد) كما هو موضح أدناه.
إذا تم تغيير/إضافة Resource Policy، يجب إعادة نشر API. يمكن أن يكون IP المعروض كـ xxx.xxx.xxx.xxx هو IP الخادم. عندما يحاول شخص ما الوصول إلى URL من IP مختلف، ستظهر الرسالة التالية:
{"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
كود JSON لـ Resource Policy سيكون كما يلي:
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. حاوية S3 العامة لاستخدامها كمجلد ويب

نحتاج إلى حاويتي S3 (bucket) للحل. تم إنشاء الأولى في الأقسام السابقة. الثانية يتم إنشاؤها الآن وستُستخدم كمجلد ويب. استُخدمت الأولى كحاوية خاصة لتخزين جميع الملفات.
بنية حاويتي S3
بنية حاويتي S3
قم بإنشاء حاوية S3 عامة كمجلد ويب. تحتوي هذه الحاوية على ملف callback.html، بحيث يمكن استخدامها كعنوان callback (إعادة الاتصال) لـ Cognito.
إنشاء Web Bucket
إنشاء Web Bucket
يجب أن تكون حاوية 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 أدناه. أضف معامل URL إضافي "state" لإرسال معامل إلى صفحة تسجيل الدخول Cognito المستضافة. سيتم تمرير معامل "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 كما هو موضح أدناه:
إنشاء Client App
إنشاء Client App
يمكن تأكيد إعدادات تطبيق Client كما هو موضح أدناه:
إعدادات App Client
إعدادات App Client
يجب تعيين اسم نطاق (domain name) ليمكن استخدامه كـ URL لـ Hosted UI.
إعداد النطاق
إعداد النطاق

7. كيفية اختبار السيناريو؟

دعنا نرى كيفية اختبار API الذي يسمح بالوصول المقيد باستخدام مجموعة مستخدمي Cognito.
يمكن لأي مستخدم نهائي النقر على رابط لبدء هذه العملية. لنفترض أن لدينا صفحة ويب تستضيف محتوى HTML التالي. كما ترى، الرابط لكل ملف هو URL الخاص بـ Cognito hosted UI.
يمكن استخدام ملف LinkToS3Files.html لاختبار السيناريو.

تنزيل ملفات الاختبار


الخلاصة

نأمل أن يكون هذا المقال مفيدًا للمبتدئين في بيئة AWS السحابية.

خدمات الحوسبة السحابية

نقدم خدمات تصميم البنية التحتية والترحيل والإدارة والتحسين على منصات AWS و Azure و Google Cloud.

استعرض خدمتنا

تواصل معنا

للحصول على معلومات مفصلة حول حلول AWS والحوسبة السحابية، تواصل مع فريقنا.

التواصل