احراز هویت، هویت یک فرد را مشخص میکند و معمولاً به عنوان ثبت نام یا ورود کاربر شناخته میشود. مجوزدهی فرآیند اعطای یا رد دسترسی به دادهها یا منابع است. به عنوان مثال، برنامه شما برای دسترسی به گوگل درایو کاربر، رضایت او را درخواست میکند.
فراخوانیهای احراز هویت و مجوزدهی باید دو جریان جداگانه و متمایز بر اساس نیازهای برنامه باشند.
اگر برنامه شما ویژگیهایی دارد که میتوانند از دادههای API گوگل استفاده کنند، اما به عنوان بخشی از ویژگیهای اصلی برنامه شما الزامی نیستند، باید برنامه خود را طوری طراحی کنید که بتواند مواردی را که دادههای API در دسترس نیستند، به خوبی مدیریت کند. به عنوان مثال، ممکن است لیستی از فایلهای ذخیره شده اخیر را زمانی که کاربر به Drive دسترسی نداده است، پنهان کنید.
شما باید فقط زمانی که کاربر عملی را انجام میدهد که نیاز به دسترسی به یک API خاص دارد، درخواست دسترسی به محدودههایی را که برای دسترسی به APIهای گوگل به آنها نیاز دارید، بدهید. به عنوان مثال، هر زمان که کاربر دکمه "ذخیره در درایو" را لمس میکند، باید درخواست دسترسی به درایو کاربر را داشته باشید.
با جدا کردن مجوزدهی از احراز هویت، میتوانید از سردرگمی کاربران جدید یا گیج شدن آنها در مورد دلیل درخواست مجوزهای خاص جلوگیری کنید.
برای احراز هویت، توصیه میکنیم از API مدیریت اعتبارنامه (Credential Manager API) استفاده کنید. برای مجوزدهی به اقداماتی که نیاز به دسترسی به دادههای کاربر ذخیره شده توسط گوگل دارند، توصیه میکنیم از AuthorizationClient استفاده کنید.
تنظیم کنید Google Cloud Console پروژه
- پروژه خود را درCloud Console یا اگر پروژهای ندارید، آن را ایجاد کنید.
- رویBranding page ، مطمئن شوید که تمام اطلاعات کامل و دقیق است.
- مطمئن شوید که نام برنامه، لوگوی برنامه و صفحه اصلی برنامه به درستی به برنامه شما اختصاص داده شده است. این مقادیر در صفحه «ورود با رضایت گوگل» هنگام ثبت نام و صفحه «برنامهها و خدمات شخص ثالث» به کاربران نمایش داده میشوند.
- مطمئن شوید که آدرسهای اینترنتی (URL) مربوط به سیاست حفظ حریم خصوصی و شرایط خدمات برنامه خود را مشخص کردهاید.
- درClients page اگر از قبل یک شناسه کلاینت اندروید برای برنامه خود ندارید، آن را ایجاد کنید. باید نام بسته برنامه و امضای SHA-1 آن را مشخص کنید.
- برو بهClients page .
- روی ایجاد کلاینت کلیک کنید.
- نوع برنامه اندروید را انتخاب کنید.
- یک نام برای کلاینت OAuth وارد کنید. این نام در پوشه پروژه شما نمایش داده میشود.Clients page برای شناسایی مشتری.
- نام بستهی برنامهی اندروید خود را وارد کنید. این مقدار در ویژگی
packageعنصر<manifest>در فایلAndroidManifest.xmlشما تعریف شده است. - اثر انگشت گواهی امضای SHA-1 مربوط به توزیع برنامه را وارد کنید.
- اگر برنامه شما از امضای برنامه توسط Google Play استفاده میکند، اثر انگشت SHA-1 را از صفحه امضای برنامه در Play Console کپی کنید.
- اگر خودتان کلید اصلی و کلیدهای امضای خود را مدیریت میکنید، از ابزار keytool که در جاوا موجود است برای چاپ اطلاعات گواهی در قالبی قابل خواندن توسط انسان استفاده کنید. مقدار
SHA-1را در بخشCertificate fingerprintsاز خروجی keytool کپی کنید. برای اطلاعات بیشتر به بخش احراز هویت کلاینت خود در مستندات Google APIs for Android مراجعه کنید. - (اختیاری) مالکیت برنامه اندروید خود را تأیید کنید .
- درClients page اگر قبلاً یک شناسه کلاینت «برنامه وب» جدید ایجاد نکردهاید، آن را ایجاد کنید. فعلاً میتوانید فیلدهای «منشاهای جاوا اسکریپت مجاز» و «URIهای تغییر مسیر مجاز» را نادیده بگیرید. این شناسه کلاینت برای شناسایی سرور backend شما هنگام ارتباط با سرویسهای احراز هویت گوگل استفاده خواهد شد.
- برو بهClients page .
- روی ایجاد کلاینت کلیک کنید.
- نوع برنامه وب را انتخاب کنید.
تأیید مالکیت برنامه
شما میتوانید مالکیت برنامه خود را تأیید کنید تا خطر جعل هویت برنامه کاهش یابد.
برای تکمیل فرآیند تأیید، میتوانید از حساب توسعهدهنده گوگل پلی خود استفاده کنید، البته اگر حساب دارید و برنامه شما در کنسول گوگل پلی ثبت شده است. برای تأیید موفقیتآمیز، شرایط زیر باید رعایت شود:
- شما باید یک برنامه ثبت شده در کنسول گوگل پلی با نام بسته و اثر انگشت گواهی امضای SHA-1 مشابه با کلاینت OAuth اندروید که در حال تکمیل تأیید اعتبار برای آن هستید، داشته باشید.
- شما باید در کنسول گوگل پلی، مجوز ادمین برای برنامه داشته باشید. درباره مدیریت دسترسی در کنسول گوگل پلی بیشتر بدانید .
در بخش «تأیید مالکیت برنامه» در کلاینت اندروید، روی دکمه «تأیید مالکیت» کلیک کنید تا فرآیند تأیید تکمیل شود.
اگر تأیید موفقیتآمیز باشد، اعلانی برای تأیید موفقیتآمیز بودن فرآیند تأیید نمایش داده میشود. در غیر این صورت، یک پیام خطا نمایش داده میشود.
برای رفع خطای تأیید ناموفق، موارد زیر را امتحان کنید:
- مطمئن شوید برنامهای که تأیید میکنید، یک برنامه ثبتشده در کنسول گوگل پلی است.
- مطمئن شوید که در کنسول گوگل پلی، مجوز ادمین برای برنامه دارید.
اعلان وابستگیها
در فایل build.gradle ماژول خود، وابستگیها را با استفاده از آخرین نسخه کتابخانه Google Identity Services تعریف کنید.
dependencies {
// ... other dependencies
implementation "com.google.android.gms:play-services-auth:21.4.0"
}
درخواست مجوزهای مورد نیاز توسط اقدامات کاربر
هر زمان که کاربری عملی را انجام میدهد که نیاز به دامنه اضافی دارد، تابع AuthorizationClient.authorize() را فراخوانی کنید. برای مثال، اگر کاربری عملی را انجام میدهد که نیاز به دسترسی به فضای ذخیرهسازی برنامه Drive خود دارد، موارد زیر را انجام دهید:
کاتلین
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) }
جاوا
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 ، پاسخ را همانطور که در قطعه کد زیر نشان داده شده است، مدیریت کنید، که در آن فرض میکنیم در یک فرگمنت انجام میشود. کد بررسی میکند که مجوزهای مورد نیاز با موفقیت اعطا شدهاند و سپس اقدام کاربر را انجام میدهد.
کاتلین
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 (ApiException e) {
// log exception
}
}
}
جاوا
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
}
});
}
اگر به APIهای گوگل در سمت سرور دسترسی دارید، متد getServerAuthCode() را از AuthorizationResult فراخوانی کنید تا یک کد مجوز دریافت کنید که برای تبادل با توکن دسترسی و بهروزرسانی به backend خود ارسال میکنید. برای کسب اطلاعات بیشتر، به بخش «حفظ دسترسی مداوم به دادههای کاربر» مراجعه کنید.
لغو مجوزها برای دادهها یا منابع کاربر
برای لغو دسترسیهای قبلاً اعطا شده، تابع AuthorizationClient.revokeAccess() را فراخوانی کنید. برای مثال، اگر کاربر حساب خود را از برنامه شما حذف میکند و برنامه شما قبلاً به DriveScopes.DRIVE_FILE دسترسی داشته است، از کد زیر برای لغو دسترسی استفاده کنید:
کاتلین
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) }
جاوا
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 پس از دریافت از سرور، به صورت محلی در حافظه پنهان (cache) ذخیره میشوند که باعث افزایش سرعت دسترسی و کاهش فراخوانیهای شبکه میشود. این توکنها پس از انقضا به طور خودکار از حافظه پنهان حذف میشوند، اما میتوانند به دلایل دیگری نیز نامعتبر شوند. اگر هنگام استفاده از یک توکن، خطای IllegalStateException دریافت کردید، حافظه پنهان محلی را پاک کنید تا مطمئن شوید که درخواست مجوز بعدی برای توکن دسترسی به سرور OAuth ارسال میشود. قطعه کد زیر، invalidAccessToken را از حافظه پنهان محلی حذف میکند:
کاتلین
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) }
جاوا
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));
دریافت اطلاعات کاربر در حین احراز هویت
پاسخ مجوزدهی حاوی هیچ اطلاعاتی در مورد حساب کاربری مورد استفاده نیست؛ پاسخ فقط شامل یک توکن برای محدودههای درخواستی است. به عنوان مثال، پاسخ مربوط به دریافت توکن دسترسی برای دسترسی به گوگل درایو کاربر، هویت حسابی که توسط کاربر انتخاب شده است را فاش نمیکند، حتی اگر بتوان از آن برای دسترسی به فایلهای موجود در درایو کاربر استفاده کرد. برای دریافت اطلاعاتی مانند نام یا ایمیل کاربر، گزینههای زیر را دارید:
قبل از درخواست مجوز، کاربر را با استفاده از APIهای Credential Manager با حساب گوگل خود وارد سیستم کنید. پاسخ احراز هویت از Credential Manager شامل اطلاعات کاربر مانند آدرس ایمیل است و همچنین حساب پیشفرض برنامه را روی حساب انتخاب شده تنظیم میکند. در صورت نیاز، میتوانید این حساب را در برنامه خود ردیابی کنید. درخواست مجوز بعدی از حساب به عنوان پیشفرض استفاده میکند و مرحله انتخاب حساب را در جریان مجوز نادیده میگیرد. برای استفاده از یک حساب دیگر برای مجوز، به بخش «مجوز از یک حساب غیر پیشفرض» مراجعه کنید.
در درخواست مجوز خود، علاوه بر محدودههایی که میخواهید (برای مثال،
Drive scope)، محدودههایuserinfo،profileوopenidرا نیز درخواست کنید. پس از اینکه توکن دسترسی بازگردانده شد، با استفاده از کتابخانه HTTP مورد نظر خود و با قرار دادن توکن دسترسی که در هدر دریافت کردهاید، معادل دستورcurlزیر، اطلاعات کاربر را با ارسال یک درخواستGETHTTP به نقطه پایانی OAuth userinfo (https://www.googleapis.com/oauth2/v3/userinfo) دریافت کنید:curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"پاسخ،
UserInfoاست که محدود به محدودههایی است که درخواست شدهاند و به صورت JSON قالببندی شده است.
مجوز از یک حساب کاربری غیر پیشفرض
اگر از Credential Manager برای احراز هویت استفاده میکنید و AuthorizationClient.authorize() را اجرا میکنید، حساب پیشفرض برنامه شما روی حسابی که کاربر انتخاب کرده است تنظیم میشود. این بدان معناست که هرگونه فراخوانی بعدی برای احراز هویت از این حساب پیشفرض استفاده میکند. برای نمایش اجباری انتخابگر حساب، کاربر را با استفاده از API clearCredentialState() از Credential Manager از برنامه خارج کنید.
دسترسی مداوم به دادههای کاربر را حفظ کنید
اگر نیاز دارید که از طریق برنامه خود به دادههای کاربر دسترسی داشته باشید، یک بار تابع AuthorizationClient.authorize() را فراخوانی کنید؛ در جلسات بعدی، و تا زمانی که مجوزهای اعطا شده توسط کاربر حذف نشده باشند، همین متد را برای دریافت توکن دسترسی جهت دستیابی به اهداف خود، بدون هیچ گونه تعاملی با کاربر، فراخوانی کنید. از طرف دیگر، اگر نیاز دارید که از طریق سرور backend خود به دادههای کاربر در حالت آفلاین دسترسی داشته باشید، باید نوع متفاوتی از توکن به نام "refresh token" را درخواست کنید.
توکنهای دسترسی عمداً طوری طراحی شدهاند که کوتاهمدت باشند و طول عمری معادل یک ساعت داشته باشند. اگر یک توکن دسترسی رهگیری یا به خطر بیفتد، بازه اعتبار محدود آن، سوءاستفاده احتمالی را به حداقل میرساند. پس از انقضا، توکن نامعتبر میشود و هرگونه تلاش برای استفاده از آن توسط سرور منبع رد میشود. از آنجایی که توکنهای دسترسی کوتاهمدت هستند، سرورها از توکنهای تازهسازی برای حفظ دسترسی مداوم به دادههای کاربر استفاده میکنند. توکنهای تازهسازی، توکنهایی با طول عمر طولانی هستند که توسط کلاینت برای درخواست یک توکن دسترسی کوتاهمدت از سرور مجوز، زمانی که توکن دسترسی قدیمی منقضی میشود، بدون هیچ گونه تعاملی با کاربر، استفاده میشوند.
برای دریافت توکن بهروزرسانی، ابتدا باید در مرحلهی تأیید در برنامهی خود، با درخواست «دسترسی آفلاین»، یک کد تأیید (یا کد مجوز) دریافت کنید و سپس کد تأیید را با یک توکن بهروزرسانی در سرور خود مبادله کنید. ذخیرهی ایمن توکنهای بهروزرسانی با طول عمر بالا در سرور بسیار مهم است زیرا میتوان از آنها بارها برای دریافت توکنهای دسترسی جدید استفاده کرد. بنابراین، به دلیل نگرانیهای امنیتی، ذخیرهی توکنهای بهروزرسانی در دستگاه اکیداً توصیه نمیشود. در عوض، آنها باید در سرورهای backend برنامه که مبادلهی توکن دسترسی در آنها انجام میشود، ذخیره شوند.
پس از ارسال کد احراز هویت به سرور بکاند برنامه شما، میتوانید با دنبال کردن مراحل موجود در راهنمای احراز هویت حساب ، آن را با یک توکن دسترسی کوتاهمدت روی سرور و یک توکن بهروزرسانی بلندمدت جایگزین کنید. این تبادل فقط باید در بکاند برنامه شما انجام شود.
کاتلین
// 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) }
جاوا
// 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"));
قطعه کد زیر فرض میکند که مجوزدهی از یک فرگمنت آغاز شده است.
کاتلین
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
}
}
}
جاوا
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
}
});
}