مجاز کردن دسترسی به داده‌های کاربر Google

اصالت‌سنجی هویت فرد را مشخص می‌کند و معمولاً به آن ثبت‌نام یا ورود به سیستم کاربر گفته می‌شود. مجوزدهی فرایند اعطا یا رد کردن دسترسی به داده‌ها یا منابع است. برای مثال، برنامه شما برای دسترسی به Google Drive کاربر، از او درخواست رضایت می‌کند.

فراخوانی‌های اصالت‌سنجی و صدور مجوز باید دو جریان جداگانه و متمایز براساس نیازهای برنامه باشند.

اگر برنامه‌تان ویژگی‌هایی دارد که می‌تواند از داده‌های Google API استفاده کند، اما این ویژگی‌ها جزو ویژگی‌های اصلی برنامه‌تان نیست، برنامه‌تان را طوری طراحی کنید که بتواند به‌خوبی از پس مواردی که داده‌های API دردسترس نیست برآید. برای مثال، وقتی کاربر دسترسی به Drive را اعطا نکرده است، می‌توانید فهرست فایل‌های اخیراً ذخیره‌شده را پنهان کنید.

فقط زمانی باید برای دسترسی به دامنه‌هایی که برای دسترسی به Google APIs نیاز دارید درخواست دهید که کاربر کنشی را انجام دهد که به دسترسی به میانای برنامه‌سازی کاربردی خاصی نیاز داشته باشد. برای مثال، هرگاه کاربر روی دکمه ذخیره در Drive تک‌ضرب می‌زند، باید برای دسترسی به Drive کاربر درخواست اجازه کنید.

با جدا کردن صدور مجوز از اصالت‌سنجی، می‌توانید از سردرگم کردن کاربران جدید یا گیج کردن کاربران درباره اینکه چرا از آن‌ها درخواست اجازه‌های خاصی می‌شود جلوگیری کنید.

برای اصالت‌سنجی، توصیه می‌کنیم از Credential Manager API استفاده کنید. برای مجاز کردن کنش‌هایی که به دسترسی به داده‌های کاربر ذخیره‌شده توسط Google نیاز دارند، توصیه می‌کنیم از AuthorizationClient استفاده کنید.

راه‌اندازی پروژه «کنسول Google Cloud»

  1. پروژه‌تان را در کنسول Cloud باز کنید، یا اگر پروژه ندارید، پروژه‌ای ایجاد کنید.
  2. در صفحه نمانام‌سازی، مطمئن شوید که همه اطلاعات کامل و دقیق باشد.
    1. مطمئن شوید که «نام برنامه»، «نشان‌واره برنامه»، و «صفحه اصلی برنامه» به‌درستی به برنامه‌تان اختصاص داده شده باشد. این مقادیر در صفحه رضایت «ورود به سیستم با Google» هنگام ثبت‌نام و در صفحه برنامه‌ها و سرویس‌های طرف سوم به کاربران نشان داده خواهد شد.
    2. مطمئن شوید نشانی‌های وب خط‌مشی رازداری و شرایط خدمات برنامه‌تان را مشخص کرده‌اید.
  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» کپی کنید.
    8. اگر keystore و کلیدهای امضای خودتان را مدیریت می‌کنید، از ابزار کمکی keytool که همراه با Java ارائه می‌شود برای چاپ اطلاعات گواهی در قالب قابل‌خواندن برای انسان استفاده کنید. مقدار SHA-1 را در بخش Certificate fingerprints خروجی keytool کپی کنید. برای اطلاعات بیشتر، به اصالت‌سنجی مشتری در Google APIs برای اسناد Android مراجعه کنید.
    9. (اختیاری) مالکیت برنامه Android خود را به‌تأیید برسانید.
  4. در صفحه مشتریان، اگر قبلاً شناسه مشتری «برنامه وب» جدیدی ایجاد نکرده‌اید، این کار را انجام دهید. می‌توانید فعلاً فیلدهای «مبدأهای مجاز جاوا اسکریپت» و «نشانی‌های وب هدایت مجاز» را نادیده بگیرید. این شناسه کارخواه برای شناسایی سرور زیرینه شما هنگام ارتباط با سرویس‌های اصالت‌سنجی Google استفاده خواهد شد.
    1. به صفحه کارخواهان بروید.
    2. روی ایجاد کارخواه کلیک کنید.
    3. نوع برنامه وب را انتخاب کنید.

به‌تأیید رساندن مالکیت برنامه

می‌توانید مالکیت برنامه‌تان را به‌تأیید برسانید تا خطر جعل هویت برنامه کاهش یابد.

برای تکمیل فرایند درستی‌سنجی، می‌توانید از «حساب توسعه‌دهنده Google Play» خود استفاده کنید (اگر حساب دارید و برنامه‌تان در کنسول Google Play ثبت شده است). برای درستی‌سنجی موفق، الزامات زیر باید برآورده شود:

  • باید برنامه‌ای ثبت‌شده در «کنسول Google Play» داشته باشید که نام بسته و اثر انگشت گواهینامه امضای SHA-1 آن با کارخواه OAuth Android که درحال تکمیل درستی‌سنجی برای آن هستید یکسان باشد.
  • باید برای برنامه در «کنسول Google Play» اجازه سرپرست داشته باشید. درباره مدیریت دسترسی در «کنسول Google Play» بیشتر بدانید.

در بخش درستی‌سنجی مالکیت برنامه در کارخواه Android، روی دکمه درستی‌سنجی مالکیت کلیک کنید تا فرایند درستی‌سنجی تکمیل شود.

اگر درستی‌سنجی موفقیت‌آمیز باشد، اعلانی برای تأیید موفقیت فرایند درستی‌سنجی نمایش داده می‌شود. درغیراین‌صورت، پیام‌واره خطا نشان داده خواهد شد.

برای رفع مشکل درستی‌سنجی ناموفق، موارد زیر را امتحان کنید:

  • مطمئن شوید برنامه‌ای که درستی‌سنجی می‌کنید در «کنسول Google Play» ثبت شده باشد.
  • مطمئن شوید که برای برنامه در «کنسول Google Play» اجازه سرپرست دارید.

اعلام وابستگی‌ها

در فایل build.gradle واحدتان، وابستگی‌ها را بااستفاده از جدیدترین نسخه کتابخانه «سرویس‌های مدیریت هویت‌سنجی Google» اعلام کنید.

dependencies {
  // ... other dependencies

  implementation "com.google.android.gms:play-services-auth:22.0.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 (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 {
            // 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 داده شده است، از کد زیر برای لغو دسترسی استفاده کنید:

کاتلین

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 پس‌از دریافت از سرور به‌صورت محلی در حافظه نهان ذخیره می‌شوند، که این امر دسترسی را تسریع می‌کند و تماس‌های شبکه را کاهش می‌دهد. این توکن‌ها پس‌از انقضا به‌طور خودکار از حافظه نهان حذف می‌شوند، اما ممکن است به دلایل دیگری نیز نامعتبر شوند. اگر هنگام استفاده از کد، 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));

دریافت اطلاعات کاربر درطول صدور مجوز

پاسخ مجوز حاوی هیچ اطلاعاتی درباره حساب کاربری استفاده‌شده نیست؛ پاسخ فقط حاوی یک توکن برای دامنه‌های درخواستی است. برای مثال، پاسخ برای دریافت کد دسترسی برای دسترسی به 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 است.

صدور مجوز از حساب غیرپیش‌فرض

اگر از «مدیر اطلاعات کاربری» برای اصالت‌سنجی استفاده می‌کنید و AuthorizationClient.authorize() را اجرا می‌کنید، حساب پیش‌فرض برنامه‌تان روی حسابی که کاربرتان انتخاب کرده است تنظیم می‌شود. این یعنی هرگونه فراخوانی بعدی برای صدور مجوز از این حساب پیش‌فرض استفاده می‌کند. برای اجبار به نمایش انتخابگر حساب، بااستفاده از Credential Manager API clearCredentialState()، کاربر را از سیستم برنامه خارج کنید.

حفظ دسترسی مداوم به داده‌های کاربر

اگر لازم است از برنامه‌تان به داده‌های کاربر دسترسی داشته باشید، AuthorizationClient.authorize() را یک‌بار فراخوانی کنید؛ در جلسه‌های بعدی، و تا زمانی که اجازه‌های اعطاشده توسط کاربر برداشته نشده است، همان روش را فراخوانی کنید تا بدون هیچ‌گونه تعامل کاربر، یک کد دسترسی برای دستیابی به اهدافتان دریافت کنید. ازسوی دیگر، اگر لازم است در حالت آفلاین از سرور زیرینه خود به داده‌های کاربر دسترسی داشته باشید، باید نوع دیگری از ژتون را درخواست کنید که «ژتون بازآوری» نامیده می‌شود.

کدهای دسترسی عمداً به‌گونه‌ای طراحی شده‌اند که کوتاه‌مدت باشند و طول عمر آن‌ها یک ساعت است. اگر کد دسترسی رهگیری یا لو برود، پنجره اعتبار محدود آن سوءاستفاده احتمالی را به‌حداقل می‌رساند. پس‌از انقضا، کد نامعتبر می‌شود و هرگونه تلاش برای استفاده از آن توسط سرور منبع رد خواهد شد. ازآنجایی‌که کدهای دسترسی کوتاه‌مدت هستند، سرورها از کدهای بازآوری برای حفظ دسترسی مداوم به داده‌های کاربر استفاده می‌کنند. ژتون‌های بازآوری ژتون‌هایی با طول عمر طولانی هستند که کارخواه از آن‌ها برای درخواست ژتون دسترسی کوتاه‌مدت از سرور مجوزدهی استفاده می‌کند، وقتی ژتون دسترسی قدیمی منقضی شده باشد، بدون هیچ‌گونه تعامل کاربر.

برای دریافت ژتون بازآوری، ابتدا باید درطول مرحله صدور مجوز در برنامه‌تان با درخواست «دسترسی آفلاین»، کد اصالت‌سنجی (یا کد مجوز) دریافت کنید و سپس کد اصالت‌سنجی را با ژتون بازآوری در سرورتان معاوضه کنید. بسیار مهم است که ژتون‌های بازآوری طولانی‌مدت را به‌طور ایمن در سرورتان ذخیره کنید چون می‌توان از آن‌ها به‌طور مکرر برای دریافت کدهای دسترسی جدید استفاده کرد. بنابراین، به‌دلیل نگرانی‌های امنیتی، اکیداً توصیه می‌شود که ژتون‌های بازآوری را در دستگاه ذخیره نکنید. درعوض، این اطلاعات باید در سرورهای زیرینه برنامه که تبادل کد دسترسی در آنجا انجام می‌شود ذخیره شود.

پس‌از ارسال کد احراز هویت به سرور زیرینه برنامه‌تان، می‌توانید با دنبال کردن مراحل موجود در راهنمای مجوز حساب، آن را با کد دسترسی کوتاه‌مدت در سرور و ژتون بازآوری طولانی‌مدت معاوضه کنید. این تبادل باید فقط در پسای برنامه شما انجام شود.

کاتلین

// 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
                }
            });
}