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

السيناريو
لنفترض أنك تقوم بتطوير بعض التطبيقات لعميلك. ومع ذلك، هناك بعض الملفات مثل 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 لتطبيق جميع الخطوات في هذا المقال.
- كيفية إنشاء حاوية S3 خاصة (Bucket)؟
- كيفية إنشاء سياسة مخصصة للوصول إلى الكائنات في حاوية S3 الخاصة؟
- كيفية إنشاء دالة Lambda للوصول إلى الكائنات في حاوية S3 الخاصة؟
- كيفية إنشاء Gateway API لاستخدام دالة Lambda؟
- كيفية إنشاء حاوية S3 عامة لاستخدامها كمجلد ويب؟
- كيفية إنشاء وتكوين مجموعة مستخدمي Cognito؟
- كيفية اختبار السيناريو؟
1. كيفية إنشاء حاوية S3 خاصة؟
S3 هي إحدى الخدمات المستندة إلى المنطقة (region-based) في AWS. تُسمى العناصر في حاويات S3 كائنات (object). لذلك، يمكن استخدام مصطلحي الكائن والملف بالتبادل لحاويات S3 في AWS.
احتفظ بخانة الاختيار "حظر جميع الوصول العام" (Block All Public Access) محددة. تم إنشاء حاوية 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):
اختر "specific" كمورد (resource) وحدد حاوية S3 الخاصة بك حتى تمتلك السياسة القدرات المطلوبة:

قم بتسمية سياستك وإنشائها. يمكنك إعطاء أي اسم ولكن ستحتاج إلى تذكره.

سيبدو ملخص سياستك المخصصة كما يلي. من الممكن أيضًا إنشاء سياسة باستخدام محتوى 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، يتم عرض كود "hello" كمثال. نحتاج إلى تطوير كودنا الخاص بدلاً من ذلك.
كما ترى، تشبه بيئة تطوير Lambda IDE خفيفة الوزن قائمة على الويب.

استبدل الكود الموجود بالكود المثال القصير المعطى. سيبدو الكود الجديد كما يلي. بعد تغيير الكود، اضغط على زر "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 على حق وصول
GetObject مقيد على حاوية S3 الخاصة بك.
هذا كل ما يجب القيام به لوصول Lambda إلى حاوية S3 الخاصة بك. الآن، حان الوقت لإنشاء طريقة AWS Gateway لاستخدام دالة Lambda الخاصة بنا.
4. إنشاء Gateway API لاستخدام دالة Lambda
قم بإنشاء AWS Gateway REST API كما هو موضح أدناه. هناك العديد من الخيارات ولكننا ننشئ "REST" API كـ "New API". قم بتسمية API Gateway الخاص بك.

هناك بعض الخطوات لإنشاء وتشغيل AWS GW API:
- إنشاء API
- إنشاء Resource
- إنشاء Method
- نشر API (Deploy)
قم بإنشاء
Resource لـ REST API الخاص بك كما هو موضح أدناه:
المورد (resource) الذي تم إنشاؤه هنا سيُستخدم لاحقًا في URL الخاص بـ API.

قم بإنشاء طريقة
GET للمورد الذي أنشأته:
هنا يمكن إنشاء أي طريقة HTTP مثل
GET أو POST أو PUT أو DELETE وما إلى ذلك. لاحتياجاتنا، نقوم بإنشاء GET فقط. لا تنس ربط دالة Lambda التي أنشأناها في الخطوات السابقة بهذه الطريقة.تم تحديد Lambda Proxy Integration هنا. يتيح لنا هذا النهج معالجة جميع المحتويات المتعلقة بالاستجابة في دالة Lambda.

بعد إنشاء طريقة
GET، سيبدو التدفق بين طريقة API Gateway ودالة Lambda كما يلي:
قم بتمكين CORS لـ Gateway API كما هو موضح أدناه. يمكن تحديد خياري Default 4xx و Default 5xx؛ وبالتالي يمكن إرجاع الأخطاء بسلاسة.

بعد إنشاء وتكوين كل شيء يتعلق بطريقة AWS Gateway، حان الوقت الآن لنشر (deploy) API. يتم نشر API إلى مرحلة (stage) كما هو موضح. كما سيُستخدم اسم هذه المرحلة في URL العام لـ API.

بعد النشر، سيبدو 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 لـ 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"}
كود 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 عامة كمجلد ويب. تحتوي هذه الحاوية على ملف
callback.html، بحيث يمكن استخدامها كعنوان callback (إعادة الاتصال) لـ Cognito.
يجب أن تكون حاوية 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=7uuggclp7269oguth08mi2ee04response_type=tokenscope=openid+profile+emailredirect_uri=https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.htmlstate=fn=testFile.pdf
state هو معامل URL مخصص. يمكن إرساله إلى صفحة Hosted UI ويُرجع إلى صفحة Callback.html.يجب إنشاء تطبيق client كما هو موضح أدناه:

يمكن تأكيد إعدادات تطبيق Client كما هو موضح أدناه:

يجب تعيين اسم نطاق (domain name) ليمكن استخدامه كـ URL لـ Hosted UI.

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