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

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

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

اگر برنامه شما ویژگی‌هایی دارد که می‌توانند از داده‌های «میانای برنامه‌سازی کاربردی Google» استفاده کنند، اما به‌عنوان بخشی از ویژگی‌های اصلی برنامه شما الزامی نیستند، برنامه‌تان را طوری طراحی کنید که بتواند در مواقعی که داده‌های «میانای برنامه‌سازی کاربردی» دردسترس نیستند، به‌خوبی از عهده این وضعیت برآید. برای مثال، ممکن است فهرست فایل‌های اخیراً ذخیره‌شده را وقتی کاربر دسترسی 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 for Android ببینید.
    9. (اختیاری) مالکیت برنامه Android خود را به‌تأیید برسانید.
  4. در صفحه «مشتریان»، اگر ازقبل شناسه مشتری «برنامه وب» جدیدی ایجاد نکرده‌اید، آن را ایجاد کنید. فعلاً می‌توانید فیلدهای «مبدأهای مجاز جاوا اسکریپت» و «نشانی‌های وب هدایت مجاز» را نادیده بگیرید. این شناسه کارخواه برای شناسایی سرور زیرینه شما هنگام ارتباط با سرویس‌های اصالت‌سنجی Google استفاده خواهد شد.
    1. به صفحه «کارخواهان» بروید.
    2. روی ایجاد مشتری کلیک کنید.
    3. نوع برنامه وب را انتخاب کنید.

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

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

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

  • باید برنامه‌ای ثبت‌شده در «کنسول Google Play» با نام بسته و اثر انگشت گواهینامه امضای SHA-1 یکسان با کارخواه Android OAuth که درستی‌سنجی آن را تکمیل می‌کنید داشته باشید.
  • باید اجازه سرپرست برای برنامه در «کنسول 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» او به سیستم وارد کنید. پاسخ اصالت‌سنجی از «مدیر اطلاعات اعتباری» شامل اطلاعات کاربر مثل نشانی ایمیل است و همچنین حساب پیش‌فرض برنامه را روی حساب انتخاب‌شده تنظیم می‌کند؛ درصورت نیاز، می‌توانید این حساب را در برنامه‌تان پیگیری کنید. درخواست مجوز بعدی از این حساب به‌عنوان پیش‌فرض استفاده می‌کند و مرحله انتخاب حساب را در جریان مجوز رد می‌کند. برای استفاده از حساب دیگری برای مجوز، مجوز از حساب غیرپیش‌فرض را ببینید.

  • در درخواست مجوز خود، علاوه‌بر محدوده‌هایی که می‌خواهید (برای مثال، Drive scope)، محدوده‌های userinfo، profile، و openid را نیز درخواست کنید. پس‌از اینکه کد دسترسی برگردانده شد، با ایجاد یک GET درخواست HTTP به نقطه پایانی OAuth userinfo (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() را اجرا می‌کنید، حساب پیش‌فرض برنامه‌تان روی حسابی که کاربرتان انتخاب کرده است تنظیم می‌شود. این یعنی هرگونه تماس بعدی برای مجوز از این حساب پیش‌فرض استفاده می‌کند. برای اجبار به نمایش انتخابگر حساب، کاربر را بااستفاده از API clearCredentialState() از Credential Manager از سیستم برنامه خارج کنید.

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

اگر نیاز دارید از برنامه‌تان به داده‌های کاربر دسترسی پیدا کنید، 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
                }
            });
}