تحدّد المصادقة هوية المستخدم، ويُشار إليها عادةً باسم اشتراك المستخدم أو تسجيل الدخول. الترخيص هو عملية منح أو رفض الوصول إلى البيانات أو الموارد. على سبيل المثال، يطلب تطبيقك موافقة المستخدم للوصول إلى Google Drive الخاص بالمستخدم.
يجب أن تكون طلبات المصادقة والترخيص عبارة عن مسارَين منفصلَين ومختلفَين بناءً على احتياجات التطبيق.
إذا كان تطبيقك يتضمّن ميزات يمكنها الاستفادة من بيانات Google API، ولكنّها ليست مطلوبة كجزء من الميزات الأساسية لتطبيقك، يمكنك تصميم تطبيقك ليكون قادرًا على التعامل بسلاسة مع الحالات التي لا يمكن فيها الوصول إلى بيانات واجهة برمجة التطبيقات. على سبيل المثال، يمكنك إخفاء قائمة بالملفات المحفوظة مؤخرًا عندما لا يمنح المستخدم إذن الوصول إلى Drive.
يجب طلب الوصول إلى النطاقات التي تحتاج إليها للوصول إلى Google APIs فقط عندما ينفّذ المستخدم إجراءً يتطلّب الوصول إلى واجهة برمجة تطبيقات معيّنة. على سبيل المثال، يجب طلب الإذن بالوصول إلى Drive الخاص بالمستخدم عندما ينقر المستخدم على الزر حفظ في Drive.
من خلال فصل التفويض عن المصادقة، يمكنك تجنُّب إرباك المستخدمين الجدد أو إثارة حيرتهم بشأن سبب طلب أذونات معيّنة.
للمصادقة، ننصح باستخدام واجهة برمجة التطبيقات Credential Manager API. بالنسبة إلى الإجراءات التي تتطلب تفويضًا للوصول إلى بيانات المستخدمين المخزّنة لدى Google، ننصح باستخدام AuthorizationClient.
إعداد مشروعك على Google Cloud Console
- افتح مشروعك في Cloud Console، أو أنشئ مشروعًا إذا لم يكن لديك مشروع.
- في صفحة "الهوية البصرية"،
تأكَّد من أنّ جميع المعلومات كاملة ودقيقة.
- تأكَّد من أنّ تطبيقك يتضمّن اسمًا وشعارًا وصفحة رئيسية صحيحة. ستظهر هذه القيم للمستخدمين على شاشة طلب الموافقة الخاصة بميزة "تسجيل الدخول باستخدام حساب Google" عند الاشتراك وعلى شاشة التطبيقات والخدمات التابعة لجهات خارجية.
- تأكَّد من تحديد عناوين URL لسياسة الخصوصية وبنود الخدمة في تطبيقك.
- في صفحة "العملاء" (Clients)،
أنشِئ معرّف عميل Android لتطبيقك إذا لم يكن لديك معرّف. يجب تحديد اسم حزمة تطبيقك وتوقيع SHA-1.
- انتقِل إلى صفحة "العملاء".
- انقر على إنشاء عميل.
- اختَر نوع التطبيق Android.
- أدخِل اسمًا لعميل OAuth. يظهر هذا الاسم في صفحة "العملاء" الخاصة بمشروعك لتحديد العميل.
- أدخِل اسم حزمة تطبيق Android. يتم تحديد هذه القيمة في السمة
packageللعنصر<manifest>في ملفAndroidManifest.xml. - أدخِل الملف المرجعي لشهادة توقيع SHA-1 لتوزيع التطبيق.
- إذا كان تطبيقك يستخدم ميزة "توقيع التطبيق" من Google Play، انسخ الملف المرجعي لشهادة SHA-1 من صفحة توقيع التطبيق في Play Console.
- إذا كنت تدير ملف تخزين المفاتيح ومفاتيح التوقيع بنفسك، استخدِم أداة keytool المضمّنة في Java لطباعة معلومات الشهادة بتنسيق يمكن لشخص عادي قراءته. انسخ قيمة
SHA-1في قسمCertificate fingerprintsمن مخرجات keytool. لمزيد من المعلومات، يُرجى الاطّلاع على مصادقة العميل في مستندات Google APIs لنظام التشغيل Android. - (اختياري) أثبِت ملكية تطبيق Android.
- في صفحة "العملاء" (Clients)،
أنشِئ معرّف عميل جديدًا لـ "تطبيق ويب" (Web application) إذا لم يسبق لك إجراء ذلك. يمكنك تجاهل الحقلين "مصادر JavaScript المسموح بها" و "معرّفات الموارد المنتظمة (URI) المعتمَدة لإعادة التوجيه" في الوقت الحالي. سيتم استخدام معرّف العميل هذا لتحديد خادم الخلفية
عندما يتواصل مع خدمات المصادقة من Google.
- انتقِل إلى صفحة "العملاء".
- انقر على إنشاء عميل.
- اختَر نوع تطبيق الويب.
إثبات ملكية التطبيق
يمكنك إثبات ملكية تطبيقك للحدّ من خطر انتحال هوية التطبيق.
لإكمال عملية التحقّق من المعلنين، يمكنك استخدام حساب المطوّر على Google Play إذا كان لديك حساب وكان تطبيقك مسجّلاً على Google Play Console. يجب استيفاء المتطلبات التالية لإكمال عملية التحقّق بنجاح:
- يجب أن يكون لديك تطبيق مسجَّل في Google Play Console يحمل اسم الحزمة نفسه والملف المرجعي لشهادة توقيع SHA-1 نفسه لعميل OAuth على Android الذي تُكمل عملية التحقّق له.
- يجب أن يكون لديك إذن المشرف للتطبيق في Google Play Console. مزيد من المعلومات حول إدارة إمكانية الوصول في Google Play Console
في قسم إثبات ملكية التطبيق في عميل Android، انقر على الزر إثبات الملكية لإكمال عملية إثبات الملكية.
إذا نجحت عملية التحقّق، سيظهر إشعار لتأكيد نجاح العملية. وإلا، سيتم عرض رسالة خطأ.
لحلّ مشكلة تعذُّر إثبات الهوية، جرِّب ما يلي:
- تأكَّد من أنّ التطبيق الذي تتحقّق منه مسجَّل في Google Play Console.
- تأكَّد من حصولك على إذن المشرف للتطبيق في Google Play Console.
تحديد الاعتماديات
في ملف build.gradle الخاص بالوحدة، يجب الإعلان عن التبعيات باستخدام أحدث إصدار من مكتبة Google Identity Services.
dependencies {
// ... other dependencies
implementation "com.google.android.gms:play-services-auth:22.0.0"
}
طلب الأذونات اللازمة لإجراءات المستخدمين
عندما ينفّذ المستخدم إجراءً يتطلّب نطاقًا إضافيًا، يمكنك استدعاء
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 قبل طلب الإذن. يتضمّن ردّ المصادقة من Credential Manager معلومات المستخدم، مثل عنوان البريد الإلكتروني، ويضبط أيضًا الحساب التلقائي للتطبيق على الحساب المحدّد. يمكنك تتبُّع هذا الحساب في تطبيقك إذا لزم الأمر. يستخدم طلب التفويض اللاحق الحساب كحساب تلقائي ويتخطّى خطوة اختيار الحساب في عملية التفويض. لاستخدام حساب مختلف للمصادقة، يُرجى الاطّلاع على المصادقة من حساب غير تلقائي.
في طلب التفويض، بالإضافة إلى النطاقات التي تريدها (على سبيل المثال،
Drive scope)، اطلب نطاقاتuserinfoوprofileوopenid. بعد عرض رمز الدخول، يمكنك الحصول على معلومات المستخدم من خلال إرسال طلب HTTP من النوعGETإلى نقطة نهاية معلومات المستخدم في 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
}
});
}