إنشاء Presigned URL لـ S3: منح وصول مؤقت للملفات الخاصة

عندما تحتاج إلى منح مستخدم وصولاً مؤقتاً لتنزيل ملف خاص من S3 دون الكشف عن بيانات الاعتماد أو تغيير صلاحيات الـ Bucket، فإن Presigned URL هو الحل الأمثل — رابط موقّع مشفر يحمل صلاحيات محددة وينتهي تلقائياً بعد فترة زمنية تحددها أنت.

ملخص سريع (TL;DR)

العنصرالتفاصيل
الهدفمنح وصول مؤقت لتنزيل ملف خاص من S3
الأداةAWS SDK (Python/Boto3 أو JavaScript)
مدة الصلاحيةقابلة للضبط — ساعة واحدة في هذا المثال
الصلاحية المطلوبةs3:GetObject على الـ Object المحدد
الأمانالرابط موقّع بمفتاح سري — لا يُكشف عن بيانات الاعتماد
القيد الأساسيإذا انتهت صلاحية الـ Credentials المستخدمة للتوقيع قبل انتهاء الرابط، يصبح الرابط غير صالح

كيف يعمل Presigned URL في S3

قبل الدخول في التطبيق، من الضروري فهم الآلية الداخلية — لأن سوء فهمها هو السبب الأكثر شيوعاً للأخطاء في الإنتاج.

عندما تولّد Presigned URL، لا يحدث أي اتصال بـ S3 في تلك اللحظة. ما يحدث هو أن الـ SDK يأخذ معلومات الطلب (اسم الـ Bucket، مسار الـ Object، مدة الصلاحية) ويوقّعها باستخدام بيانات الاعتماد المتاحة محلياً. الرابط الناتج يحمل هذا التوقيع مضمّناً فيه. عندما يستخدم المستخدم الرابط، يتحقق S3 من صحة التوقيع ومن أن الوقت الحالي لا يتجاوز وقت الانتهاء المضمّن في الرابط.

فكّر في الأمر كتذكرة دخول موقّعة من مدير المبنى — التذكرة نفسها تحمل الإذن، ولا تحتاج إلى مراجعة المدير في كل مرة تدخل فيها.

النقطة الحرجة: إذا كانت بيانات الاعتماد المستخدمة للتوقيع هي Temporary Credentials (مثل IAM Role أو STS Token)، فإن الرابط يصبح غير صالح فور انتهاء صلاحية تلك الـ Credentials — حتى لو لم تنتهِ مدة الرابط بعد. هذا سلوك موثّق في AWS وليس خطأً.

sequenceDiagram participant App as تطبيقك participant SDK as AWS SDK participant User as المستخدم participant S3 as Amazon S3 App->>SDK: generate_presigned_url(bucket, key, 3600) Note over SDK: توقيع محلي فقط
لا يوجد طلب لـ S3 SDK-->>App: رابط موقّع (HTTPS URL) App-->>User: إرسال الرابط User->>S3: GET طلب مباشر بالرابط S3->>S3: التحقق من التوقيع والوقت S3-->>User: محتوى الملف
  1. التوليد: تطبيقك يستدعي الـ SDK محلياً — لا يوجد طلب لـ S3 في هذه المرحلة.
  2. التوقيع: الـ SDK يوقّع معلمات الطلب باستخدام بيانات الاعتماد المتاحة (AWS Signature Version 4).
  3. التسليم: ترسل الرابط الموقّع للمستخدم عبر أي قناة (بريد إلكتروني، API response، إلخ).
  4. الاستخدام: المستخدم يرسل طلب HTTP مباشرة لـ S3 باستخدام الرابط.
  5. التحقق: S3 يتحقق من التوقيع والوقت — إذا كلاهما صحيح، يُرسل الملف مباشرة.

المتطلبات: صلاحيات IAM اللازمة لإنشاء Presigned URL

الكيان الذي يولّد الرابط (Lambda Function، EC2 Instance، مستخدم IAM) يحتاج إلى صلاحية s3:GetObject على الـ Object المستهدف. بدونها، الرابط الناتج سيُرجع خطأ 403 Forbidden عند الاستخدام — وليس عند التوليد. هذا هو مصدر الارتباك الأكثر شيوعاً.

🔽 IAM Policy — الحد الأدنى من الصلاحيات
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowGetObjectForPresigning",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}

إذا كنت تريد تقييد الوصول لمسار محدد فقط، استبدل * بـ private/uploads/* أو أي بادئة تناسب بنية ملفاتك.

إنشاء Presigned URL باستخدام Python (Boto3)

هذا هو النمط الأكثر استخداماً في الإنتاج — بسيط ومباشر:

import boto3
from botocore.exceptions import ClientError

def generate_presigned_url(bucket_name, object_key, expiration_seconds=3600):
    """
    توليد Presigned URL لتنزيل ملف خاص من S3.

    :param bucket_name: اسم الـ S3 Bucket
    :param object_key: مسار الملف داخل الـ Bucket
    :param expiration_seconds: مدة الصلاحية بالثواني (الافتراضي: 3600 = ساعة واحدة)
    :return: الرابط الموقّع أو None في حالة الخطأ
    """
    s3_client = boto3.client('s3', region_name='us-east-1')

    try:
        url = s3_client.generate_presigned_url(
            ClientMethod='get_object',
            Params={
                'Bucket': bucket_name,
                'Key': object_key
            },
            ExpiresIn=expiration_seconds
        )
        return url
    except ClientError as e:
        print(f'خطأ في توليد الرابط: {e}')
        return None

# مثال على الاستخدام
url = generate_presigned_url(
    bucket_name='my-private-bucket',
    object_key='documents/report-2024.pdf',
    expiration_seconds=3600
)

if url:
    print(f'الرابط المؤقت: {url}')

لاحظ أن ExpiresIn يقبل القيمة بالثواني — 3600 تساوي ساعة واحدة. الحد الأقصى الموثّق للـ Presigned URL المولّدة باستخدام IAM User Credentials هو 7 أيام (604800 ثانية). أما إذا كنت تستخدم IAM Role أو STS Credentials، فإن الحد الأقصى الفعلي مقيّد بمدة صلاحية الـ Session Token.

إنشاء Presigned URL باستخدام JavaScript (AWS SDK v3)

إذا كان تطبيقك Node.js أو تعمل في بيئة serverless بـ JavaScript:

🔽 JavaScript SDK v3 — توليد Presigned URL
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3Client = new S3Client({ region: 'us-east-1' });

async function generatePresignedUrl(bucketName, objectKey, expiresInSeconds = 3600) {
  const command = new GetObjectCommand({
    Bucket: bucketName,
    Key: objectKey,
  });

  try {
    const url = await getSignedUrl(s3Client, command, {
      expiresIn: expiresInSeconds,
    });
    return url;
  } catch (error) {
    console.error('خطأ في توليد الرابط:', error);
    throw error;
  }
}

// مثال على الاستخدام
const url = await generatePresignedUrl(
  'my-private-bucket',
  'documents/report-2024.pdf',
  3600
);
console.log('الرابط المؤقت:', url);

الـ SDK v3 يستخدم نمط Command منفصل عن الـ v2 — getSignedUrl مستوردة من @aws-sdk/s3-request-presigner وليس من الـ Client مباشرة. هذا تغيير معماري في الـ v3 يُربك كثيراً من المطورين القادمين من الـ v2.

توليد Presigned URL عبر AWS CLI

مفيد للاختبار السريع أو في سكريبتات الـ Shell:

aws s3 presign s3://my-private-bucket/documents/report-2024.pdf \
  --expires-in 3600 \
  --region us-east-1

الأمر يُرجع الرابط مباشرة في الـ terminal. --expires-in يقبل القيمة بالثواني.

تشخيص المشكلة الأكثر شيوعاً: الرابط صحيح لكن يُرجع 403

هذا هو النمط الذي يضيع فيه وقت كثير من المهندسين: الكود يعمل ويولّد رابطاً، لكن المستخدم يحصل على AccessDenied عند محاولة التنزيل.

الافتراض الأول دائماً يكون 'الرابط انتهت صلاحيته' — لكن في الغالب السبب مختلف تماماً.

graph TD A["الرابط يُرجع 403"] --> B{"هل انتهت مدة الرابط؟"} B -- نعم --> C["أعد توليد رابط جديد"] B -- لا --> D{"هل يوجد Explicit Deny
في Bucket Policy؟"} D -- نعم --> E["راجع وعدّل Bucket Policy"] D -- لا --> F{"هل الـ Credentials
Temporary وانتهت؟"} F -- نعم --> G["تجديد الـ Session Token
وإعادة توليد الرابط"] F -- لا --> H{"هل يملك الكيان
s3:GetObject؟"} H -- لا --> I["أضف الصلاحية لـ IAM Policy"] H -- نعم --> J["تحقق من تطابق الـ Region
بين الـ Client والـ Bucket"]
  1. التحقق من صلاحيات IAM: تأكد أن الكيان الذي يولّد الرابط يملك s3:GetObject على الـ Object المحدد.
  2. Bucket Policy: إذا كان هناك Bucket Policy يحتوي على Explicit Deny، فإنه يتجاوز صلاحيات IAM — تحقق منها أولاً.
  3. انتهاء الـ Session Token: إذا كانت بيانات الاعتماد Temporary، تحقق من وقت انتهائها.
  4. عدم تطابق الـ Region: إذا أنشأت الـ Client بـ Region مختلفة عن Region الـ Bucket، قد تحصل على أخطاء توقيع.

للتحقق من Bucket Policy الحالية:

aws s3api get-bucket-policy \
  --bucket my-private-bucket \
  --region us-east-1

للتحقق من صلاحيات الـ IAM Entity الحالية:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/MyAppRole \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::my-private-bucket/documents/report-2024.pdf

اعتبارات أمنية عملية

Presigned URL آمن بطبيعته، لكن هناك قرارات تصميمية تؤثر على مستوى الأمان الفعلي:

  • مدة الصلاحية: اختر أقصر مدة تكفي للاستخدام المقصود. ساعة واحدة مناسبة لمعظم حالات التنزيل المباشر.
  • لا تُخزّن الروابط: الرابط يحمل التوقيع مضمّناً — تسريبه يعني منح وصول للملف حتى انتهاء المدة.
  • S3 Block Public Access: Presigned URL يعمل حتى لو كان Block Public Access مفعّلاً — الرابط يعتمد على صلاحيات IAM وليس على Public Access.
  • HTTPS فقط: الروابط الناتجة تستخدم HTTPS بشكل افتراضي — لا تحوّلها لـ HTTP.

الخلاصة والخطوات التالية لإنشاء Presigned URL

إنشاء Presigned URL في S3 عملية مباشرة: صلاحية s3:GetObject على الكيان المولّد، استدعاء generate_presigned_url مع تحديد مدة الصلاحية، وتسليم الرابط للمستخدم. المشاكل الحقيقية تظهر عند استخدام Temporary Credentials أو عند وجود Bucket Policy يحتوي على Explicit Deny.

للتعمق أكثر، راجع:

مسرد المصطلحات

المصطلحالتعريف
Presigned URLرابط URL موقّع مشفر يمنح وصولاً مؤقتاً لـ Object محدد في S3 دون الحاجة لبيانات اعتماد
AWS Signature Version 4بروتوكول التوقيع المستخدم لمصادقة الطلبات لـ AWS — يُضمّن في الرابط الموقّع
Temporary Credentialsبيانات اعتماد مؤقتة صادرة عن STS أو IAM Role — لها وقت انتهاء يؤثر على صلاحية الرابط
Explicit Denyقاعدة في IAM Policy أو Bucket Policy ترفض الوصول صراحةً — تتجاوز أي Allow في أي مكان آخر
ExpiresInالمعامل الذي يحدد مدة صلاحية الرابط بالثواني في استدعاء الـ SDK

Related Posts

تعليقات

المشاركات الشائعة من هذه المدونة

استرجاع معرّف نسخة EC2 عبر خدمة البيانات الوصفية IMDSv2

أوضاع سعة DynamoDB: متى تختار On-Demand ومتى تختار Provisioned؟

فهم مهلة الرؤية في SQS: لماذا تُعالَج رسائلك مرتين؟