تفويض الوصول إلى بيانات مستخدمي Google

تحدّد المصادقة هوية المستخدم، ويُشار إليها عادةً باسم تسجيل المستخدم أو تسجيل الدخول. التفويض هو عملية منح إذن الوصول إلى البيانات أو الموارد أو رفضه. على سبيل المثال، يطلب تطبيقك موافقة المستخدم للوصول إلى Google Drive.

يجب أن تكون طلبات المصادقة والتفويض عبارة عن مسارَين منفصلَين ومميّزَين استنادًا إلى احتياجات التطبيق.

إذا كان تطبيقك يتضمّن ميزات يمكنها استخدام بيانات Google API، ولكنّها ليست مطلوبة كجزء من الميزات الأساسية لتطبيقك، صمِّم تطبيقك بحيث يكون قادرًا على التعامل بسلاسة مع الحالات التي لا يمكن فيها الوصول إلى بيانات واجهة برمجة التطبيقات. على سبيل المثال، يمكنك إخفاء قائمة بالملفات المحفوظة مؤخرًا عندما لا يمنح المستخدم إذن الوصول إلى Drive.

يجب أن تطلب الوصول إلى النطاقات التي تحتاج إليها للوصول إلى Google APIs فقط عندما ينفّذ المستخدم إجراءً يتطلّب الوصول إلى واجهة برمجة تطبيقات معيّنة. على سبيل المثال، يجب أن تطلب إذن الوصول إلى Drive عندما ينقر المستخدم على الزر حفظ في Drive.

من خلال فصل التفويض عن المصادقة، يمكنك تجنُّب إرباك المستخدمين الجدد أو إحداث لبس لدى المستخدمين بشأن سبب طلب أذونات معيّنة.

للمصادقة، ننصحك باستخدام Credential Manager API. لتفويض الإجراءات التي تحتاج إلى الوصول إلى بيانات المستخدم المخزّنة بواسطة Google، ننصحك باستخدام AuthorizationClient.

إعداد مشروعك على Google Cloud Console

  1. افتح مشروعك في Cloud Console، أو أنشئ مشروعًا إذا لم يكن لديك مشروع حاليًا.
  2. في صفحة العلامة التجارية، تأكَّد من أنّ جميع المعلومات كاملة ودقيقة.
    1. تأكَّد من أنّ تطبيقك يتضمّن اسم تطبيق وشعار تطبيق وصفحة رئيسية للتطبيق بشكل صحيح. ستظهر هذه القيم للمستخدمين على شاشة طلب الموافقة على "تسجيل الدخول باستخدام حساب Google" عند التسجيل وعلى شاشة "التطبيقات والخدمات الخارجية".
    2. تأكَّد من تحديد عناوين URL لسياسة الخصوصية وبنود الخدمة في تطبيقك.
  3. في صفحة العملاء، أنشئ معرّف عميل Android لتطبيقك إذا لم يكن لديك معرّف حاليًا. عليك تحديد اسم حزمة تطبيقك وتوقيع SHA-1.
    1. انتقِل إلى صفحة العملاء.
    2. انقر على إنشاء عميل.
    3. اختَر نوع تطبيق Android.
    4. أدخِل اسمًا لعميل OAuth. يظهر هذا الاسم على صفحة العملاء في مشروعك لتحديد العميل.
    5. أدخِل اسم حزمة تطبيق Android. يتم تحديد هذه القيمة في السمة package للعنصر <manifest> في ملف AndroidManifest.xml.
    6. أدخِل الملف المرجعي لشهادة توقيع SHA-1 الخاصة بتوزيع التطبيق.
    7. إذا كان تطبيقك يستخدم ميزة "توقيع التطبيق" من Google Play، انسخ الملف المرجعي لشهادة SHA-1 من صفحة "توقيع التطبيق" في Play Console.
    8. إذا كنت تدير بنفسك ملف تخزين المفاتيح ومفاتيح التوقيع، استخدِم الأداة keytool المضمّنة في Java لطباعة معلومات الشهادة بتنسيق يمكن لشخص عادي قراءتها. انسخ قيمة SHA-1 في قسم Certificate fingerprints من ناتج keytool. لمزيد من المعلومات، اطّلِع على مقالة المصادقة على عميلك في مستندات Google APIs for Android.
    9. (اختياري) أثبِت ملكية تطبيق Android.
  4. في صفحة العملاء، أنشئ معرّف عميل جديدًا من نوع "تطبيق ويب" إذا لم يكن لديك معرّف حاليًا. يمكنك تجاهل الحقلَين "مصادر JavaScript المسموح بها" و"معرّفات الموارد المنتظمة (URI) المسموح بها لإعادة التوجيه" في الوقت الحالي. سيتم استخدام معرّف العميل هذا لتحديد الخادم الخلفي عند التواصل مع خدمات المصادقة من Google.
    1. انتقِل إلى صفحة العملاء.
    2. انقر على إنشاء عميل.
    3. اختَر نوع تطبيق الويب.

إثبات ملكية التطبيق

يمكنك إثبات ملكية تطبيقك للحدّ من خطر انتحال شخصية التطبيق.

لإكمال عملية إثبات الملكية، يمكنك استخدام حساب المطوّر على Google Play إذا كان لديك حساب وكان تطبيقك مسجّلاً على Google Play Console. يجب استيفاء المتطلبات التالية لإثبات الملكية بنجاح:

  • يجب أن يكون لديك تطبيق مسجَّل في Google Play Console يحمل اسم الحزمة نفسه والملف المرجعي لشهادة توقيع SHA-1 نفسه لعميل Android OAuth الذي تُكمل عملية إثبات الملكية له.
  • يجب أن يكون لديك إذن مشرف للتطبيق في Google Play Console. مزيد من المعلومات عن إدارة أذونات الوصول في Google Play Console.

في قسم إثبات ملكية التطبيق في عميل Android، انقر على الزر إثبات الملكية لإكمال عملية إثبات الملكية.

إذا نجحت عملية إثبات الملكية، سيظهر إشعار يؤكّد نجاحها. وإلا، سيظهر طلب خطأ.

لإصلاح عملية إثبات ملكية فاشلة، جرِّب ما يلي:

  • تأكَّد من أنّ التطبيق الذي تُثبِت ملكيته هو تطبيق مسجَّل في Google Play Console.
  • تأكَّد من أنّ لديك إذن مشرف للتطبيق في Google Play Console.

تحديد الاعتماديات

في ملف build.gradle الخاص بالوحدة، حدِّد الاعتماديات باستخدام أحدث إصدار من مكتبة "خدمات هوية Google".

dependencies {
  // ... other dependencies

  implementation "com.google.android.gms:play-services-auth:21.5.1"
}

طلب الأذونات المطلوبة للإجراءات التي يتّخذها المستخدم

عندما ينفّذ المستخدم إجراءً يتطلّب نطاقًا إضافيًا، استخدِم AuthorizationClient.authorize(). على سبيل المثال، إذا نفّذ المستخدم إجراءً يتطلّب الوصول إلى مساحة تخزين تطبيق Drive، اتّبِع الخطوات التالية:

Kotlin

val requestedScopes: List<Scope> = listOf(DriveScopes.DRIVE_FILE)
val authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .build()

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequestBuilder.build())
    .addOnSuccessListener { authorizationResult ->
        if (authorizationResult.hasResolution()) {
            val pendingIntent = authorizationResult.pendingIntent
            // Access needs to be granted by the user
            startAuthorizationIntent.launch(IntentSenderRequest.Builder(pendingIntent!!.intentSender).build())
        } else {
            // Access was previously granted, continue with user action
            saveToDriveAppFolder(authorizationResult);
        }
    }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }

Java

List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .build();

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequest)
    .addOnSuccessListener(authorizationResult -> {
        if (authorizationResult.hasResolution()) {
            // Access needs to be granted by the user
            startAuthorizationIntent.launch(
                new IntentSenderRequest.Builder(
                    authorizationResult.getPendingIntent().getIntentSender()
                ).build()
            );
        } else {
            // Access was previously granted, continue with user action
            saveToDriveAppFolder(authorizationResult);
        }
    })
    .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));

عند تحديد ActivityResultLauncher، تعامَل مع الردّ كما هو موضّح في المقتطف التالي، حيث نفترض أنّه يتم ذلك في جزء. يتحقّق الرمز من منح الأذونات المطلوبة بنجاح، ثم ينفّذ إجراء المستخدم.

Kotlin

private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
): View? {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
            try {
                // extract the result
                val authorizationResult = Identity.getAuthorizationClient(requireContext())
                    .getAuthorizationResultFromIntent(activityResult.data)
                // continue with user action
                saveToDriveAppFolder(authorizationResult);
            } catch (e: ApiException) {
                // log exception
            }
        }
}

Java

private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;

@Override
public View onCreateView(
    @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
startAuthorizationIntent =
    registerForActivityResult(
        new ActivityResultContracts.StartIntentSenderForResult(),
        activityResult -> {
            try {
            // extract the result
            AuthorizationResult authorizationResult =
                Identity.getAuthorizationClient(requireActivity())
                    .getAuthorizationResultFromIntent(activityResult.getData());
            // continue with user action
            saveToDriveAppFolder(authorizationResult);
            } catch (ApiException e) {
            // log exception
            }
        });
}

إذا كنت تصل إلى Google APIs على جانب الخادم، استخدِم طريقة getServerAuthCode() من AuthorizationResult للحصول على رمز تفويض ترسله إلى الخادم الخلفي لاستبداله برمز دخول و الرمز المميز لإعادة التحميل. لمزيد من المعلومات، اطّلِع على مقالة الاحتفاظ بإذن الوصول المستمر إلى بيانات المستخدم.

إبطال الأذونات للوصول إلى بيانات المستخدم أو موارده

لإبطال إذن الوصول الذي تم منحه سابقًا، استخدِم AuthorizationClient.revokeAccess(). على سبيل المثال، إذا كان المستخدم يزيل حسابه من تطبيقك، وكان تطبيقك قد حصل سابقًا على إذن الوصول إلى DriveScopes.DRIVE_FILE، استخدِم الرمز التالي لإبطال إذن الوصول:

Kotlin

val requestedScopes: MutableList<Scope> = mutableListOf(DriveScopes.DRIVE_FILE)
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
    .setAccount(account)
    .setScopes(requestedScopes)
    .build()

Identity.getAuthorizationClient(activity)
    .revokeAccess(revokeAccessRequest)
    .addOnSuccessListener { Log.i(TAG, "Successfully revoked access") }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to revoke access", e) }

Java

List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
    .setAccount(account)
    .setScopes(requestedScopes)
    .build();

Identity.getAuthorizationClient(activity)
    .revokeAccess(revokeAccessRequest)
    .addOnSuccessListener(unused -> Log.i(TAG, "Successfully revoked access"))
    .addOnFailureListener(e -> Log.e(TAG, "Failed to revoke access", e));

محو ذاكرة التخزين المؤقت للرموز المميّزة

يتم تخزين رموز الدخول إلى OAuth مؤقتًا محليًا عند استلامها من الخادم، ما يؤدي إلى تسريع إذن الوصول وتقليل طلبات الشبكة. تُحذف هذه الرموز المميّزة تلقائيًا من ذاكرة التخزين المؤقت عند انتهاء صلاحيتها، ولكن يمكن أن تصبح غير صالحة لأسباب أخرى أيضًا. إذا تلقّيت IllegalStateException عند استخدام رمز مميّز، امحُ ذاكرة التخزين المؤقت المحلية للتأكّد من أنّ طلب التفويض التالي للحصول على رمز دخول يتم إرساله إلى خادم OAuth. يزيل المقتطف التالي invalidAccessToken من ذاكرة التخزين المؤقت المحلية:

Kotlin

Identity.getAuthorizationClient(activity)
    .clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
    .addOnSuccessListener { Log.i(TAG, "Successfully removed the token from the cache") }
    .addOnFailureListener{ e -> Log.e(TAG, "Failed to clear token", e) }

Java

Identity.getAuthorizationClient(activity)
    .clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
    .addOnSuccessListener(unused -> Log.i(TAG, "Successfully removed the token from the cache"))
    .addOnFailureListener(e -> Log.e(TAG, "Failed to clear the token cache", e));

الحصول على معلومات المستخدم أثناء التفويض

لا يحتوي ردّ التفويض على أي معلومات عن حساب المستخدم الذي تم استخدامه، بل يحتوي فقط على رمز مميّز للنطاقات المطلوبة. على سبيل المثال، لا يكشف الردّ على الحصول على رمز دخول للوصول إلى Google Drive عن هوية الحساب الذي اختاره المستخدم، على الرغم من أنّه يمكن استخدامه للوصول إلى الملفات على Drive. للحصول على معلومات مثل اسم المستخدم أو بريده الإلكتروني، لديك الخيارات التالية:

  • سجِّل دخول المستخدم باستخدام حسابه على Google باستخدام Credential Manager APIs قبل طلب التفويض. يتضمّن ردّ المصادقة من Credential Manager معلومات المستخدم، مثل عنوان البريد الإلكتروني، ويضبط أيضًا الحساب التلقائي للتطبيق على الحساب المحدّد. إذا لزم الأمر، يمكنك تتبُّع هذا الحساب في تطبيقك. يستخدم طلب التفويض اللاحق الحساب كحساب تلقائي ويتخطّى خطوة اختيار الحساب في مسار التفويض. لاستخدام حساب مختلف للتفويض، اطّلِع على مقالة التفويض من حساب غير تلقائي.

  • في طلب التفويض، اطلب النطاقات التي تريدها (على سبيل المثال، Drive scope) بالإضافة إلى النطاقات userinfo وprofile وopenid. بعد عرض رمز الدخول، احصل على معلومات المستخدم من خلال إجراء طلب GET HTTP إلى نقطة نهاية معلومات المستخدم في OAuth‏ (https://www.googleapis.com/oauth2/v3/userinfo) باستخدام مكتبة HTTP المفضّلة لديك وتضمين رمز الدخول الذي تلقّيته في العنوان، وهو ما يعادل أمر curl التالي:

    curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"
    

    الردّ هو UserInfo، ويقتصر على النطاقات التي تم طلبها، ويكون بتنسيق JSON.

التفويض من حساب غير تلقائي

إذا كنت تستخدم Credential Manager للمصادقة وتنفّذ AuthorizationClient.authorize()، يتم ضبط الحساب التلقائي لتطبيقك على الحساب الذي اختاره المستخدم. وهذا يعني أنّ أي طلبات تفويض لاحقة تستخدم هذا الحساب التلقائي. لفرض عرض أداة اختيار الحساب، سجِّل خروج المستخدم من التطبيق باستخدام واجهة برمجة التطبيقات clearCredentialState() من Credential Manager.

الاحتفاظ بإذن الوصول المستمر إلى بيانات المستخدم

إذا كنت بحاجة إلى الوصول إلى بيانات المستخدم من تطبيقك، استخدِم AuthorizationClient.authorize() مرة واحدة. في الجلسات اللاحقة، وطالما لم يزِل المستخدم الأذونات الممنوحة، استخدِم الطريقة نفسها للحصول على رمز دخول لتحقيق أهدافك، بدون أي تفاعل من المستخدم. من ناحية أخرى، إذا كنت بحاجة إلى الوصول إلى بيانات المستخدم في وضع عدم الاتصال بالإنترنت من خادم الخلفية، فعليك طلب نوع مختلف من الرموز المميّزة يُعرف باسم "الرمز المميز لإعادة التحميل".

تم تصميم رموز الدخول عن قصد لتكون قصيرة الأمد وتستمر لمدة ساعة واحدة. إذا تم اعتراض رمز الدخول أو اختراقه، فإنّ فترة صلاحيته المحدودة تقلّل من احتمالية إساءة استخدامه. بعد انتهاء صلاحية الرمز المميّز، يصبح غير صالح، وسيرفض خادم المورد أي محاولات لاستخدامه. بما أنّ رموز الدخول قصيرة الأمد، تستخدم الخوادم رموز إعادة التحميل للحفاظ على إذن الوصول المستمر إلى بيانات المستخدم. رموز إعادة التحميل هي رموز مميّزة طويلة الأمد يستخدمها العميل لطلب رمز دخول قصير الأمد من خادم التفويض، عندما تنتهي صلاحية رمز الدخول القديم، بدون أي تفاعل من المستخدم.

للحصول على الرمز المميز لإعادة التحميل، عليك أولاً الحصول على رمز تفويض أثناء خطوة التفويض في تطبيقك من خلال طلب "إذن الوصول في وضع عدم الاتصال"، ثم استبدال رمز التفويض بالرمز المميز لإعادة التحميل على خادمك. من المهم جدًا تخزين رموز إعادة التحميل الطويلة الأمد بشكل آمن على خادمك لأنّه يمكن استخدامها بشكل متكرّر للحصول على رموز دخول جديدة. لذلك، يُنصح بشدة بعدم تخزين رموز إعادة التحميل على الجهاز بسبب المخاوف الأمنية. بدلاً من ذلك، يجب تخزينها في الخوادم الخلفية للتطبيق حيث يتم استبدالها برمز دخول.

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

Kotlin

// Ask for offline access during the first authorization request
val authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .requestOfflineAccess(serverClientId)
    .build()

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequest)
    .addOnSuccessListener { authorizationResult ->
        startAuthorizationIntent.launch(IntentSenderRequest.Builder(
            pendingIntent!!.intentSender
        ).build())
    }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }

Java

// Ask for offline access during the first authorization request
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .requestOfflineAccess(serverClientId)
    .build();

Identity.getAuthorizationClient(getContext())
    .authorize(authorizationRequest)
    .addOnSuccessListener(authorizationResult -> {
        startAuthorizationIntent.launch(
            new IntentSenderRequest.Builder(
                authorizationResult.getPendingIntent().getIntentSender()
            ).build()
        );
    })
    .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize"));

يفترض المقتطف التالي أنّ عملية التفويض تبدأ من جزء.

Kotlin

private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
): View? {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
            try {
                val authorizationResult = Identity.getAuthorizationClient(requireContext())
                    .getAuthorizationResultFromIntent(activityResult.data)
                // short-lived access token
                accessToken = authorizationResult.accessToken
                // store the authorization code used for getting a refresh token safely to your app's backend server
                val authCode: String = authorizationResult.serverAuthCode
                storeAuthCodeSafely(authCode)
            } catch (e: ApiException) {
                // log exception
            }
        }
}

Java

private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;

@Override
public View onCreateView(
    @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(
            new ActivityResultContracts.StartIntentSenderForResult(),
            activityResult -> {
                try {
                    AuthorizationResult authorizationResult =
                        Identity.getAuthorizationClient(requireActivity())
                            .getAuthorizationResultFromIntent(activityResult.getData());
                    // short-lived access token
                    accessToken = authorizationResult.getAccessToken();
                    // store the authorization code used for getting a refresh token safely to your app's backend server
                    String authCode = authorizationResult.getServerAuthCode()
                    storeAuthCodeSafely(authCode);
                } catch (ApiException e) {
                    // log exception
                }
            });
}