מתן הרשאה לגישה לנתוני משתמשים ב-Google

אימות קובע מי האדם, ובדרך כלל הוא נקרא הרשמה או כניסה של משתמש. הרשאה היא תהליך שבו מאשרים או דוחים גישה לנתונים או למשאבים. לדוגמה, האפליקציה מבקשת את הסכמת המשתמש לגישה ל-Google Drive שלו.

קריאות לאימות ולהרשאה צריכות להיות שני תהליכים נפרדים ושונים, בהתאם לצרכים של האפליקציה.

אם לאפליקציה שלכם יש תכונות שיכולות להשתמש בנתוני Google API, אבל הן לא נדרשות כחלק מהתכונות העיקריות של האפליקציה, כדאי לתכנן את האפליקציה כך שהיא תוכל לטפל בצורה חלקה במקרים שבהם נתוני ה-API לא נגישים. לדוגמה, אפשר להסתיר רשימה של קבצים שנשמרו לאחרונה אם המשתמש לא העניק גישה ל-Drive.

כדאי לבקש גישה להיקפי הרשאות שנדרשים לכם כדי לגשת לממשקי Google API רק כשהמשתמש מבצע פעולה שדורשת גישה לממשק API מסוים. לדוגמה, צריך לבקש הרשאה לגשת ל-Drive של המשתמש בכל פעם שהוא מקיש על הלחצן 'שמירה ב-Drive'.

הפרדה בין הרשאה לאימות יכולה למנוע מצב שבו משתמשים חדשים מרגישים מוצפים, או מצב שבו משתמשים לא מבינים למה הם מתבקשים לתת הרשאות מסוימות.

לצורך אימות, מומלץ להשתמש ב-Credential Manager API. כדי לאשר פעולות שדורשות גישה לנתוני משתמשים שמאוחסנים על ידי Google, מומלץ להשתמש ב-AuthorizationClient.

הגדרת הפרויקט

  1. פותחים את הפרויקט ב-, או יוצרים פרויקט אם עדיין אין לכם אחד.
  2. בדף , מוודאים שכל המידע מלא ומדויק.
    1. מוודאים שהוקצו לאפליקציה שם אפליקציה, לוגו אפליקציה ודף הבית של האפליקציה נכונים. הערכים האלה יוצגו למשתמשים במסך ההסכמה של 'כניסה באמצעות חשבון Google' במהלך ההרשמה ובמסך האפליקציות והשירותים של צד שלישי.
    2. חשוב לוודא שציינתם את כתובות ה-URL של מדיניות הפרטיות ושל התנאים וההגבלות של האפליקציה.
  3. ב-, יוצרים מזהה לקוח של Android לאפליקציה, אם עדיין אין כזה. תצטרכו לציין את שם החבילה של האפליקציה ואת חתימת SHA-1.
    1. עוברים אל .
    2. לוחצים על יצירת לקוח.
    3. בוחרים בסוג האפליקציה Android.
  4. בקטע , יוצרים Client-ID חדש מסוג 'אפליקציית אינטרנט' אם עוד לא עשיתם זאת. בינתיים אפשר להתעלם מהשדות Authorized JavaScript Origins (מקורות JavaScript מורשים) ו-Authorized redirect URIs (כתובות URI מורשות להפניה אוטומטית). מזהה הלקוח הזה ישמש לזיהוי שרת הקצה העורפי שלכם כשהוא מתקשר עם שירותי האימות של Google.
    1. עוברים אל .
    2. לוחצים על יצירת לקוח.
    3. בוחרים בסוג Web application.

הצהרה על יחסי תלות

בקובץ build.gradle של המודול, מצהירים על תלות באמצעות הגרסה האחרונה של ספריית Google Identity Services.

dependencies {
  // ... other dependencies

  implementation "com.google.android.gms:play-services-auth:21.4.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.launchIntentSenderRequest.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 (ApiException e) {
                // 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 API בצד השרת, אתם צריכים לקרוא ל-method ‏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 שלו באמצעות ממשקי ה-API של Credential Manager. תגובת האימות מ-Credential Manager כוללת פרטי משתמש כמו כתובת האימייל, וגם מגדירה את חשבון ברירת המחדל של האפליקציה לחשבון שנבחר. אם צריך, אפשר לעקוב אחרי החשבון הזה באפליקציה. בקשת הרשאה עוקבת משתמשת בחשבון כברירת מחדל ומדלגת על שלב בחירת החשבון בתהליך ההרשאה. כדי להשתמש בחשבון אחר להרשאה, אפשר לעיין במאמר בנושא הרשאה מחשבון שאינו ברירת המחדל.

  • בבקשת ההרשאה, בנוסף להיקפי ההרשאות שרוצים (לדוגמה, Drive scope), צריך לבקש את היקפי ההרשאות userinfo, profile ו-openid. אחרי שאסימון הגישה מוחזר, מקבלים את פרטי המשתמש על ידי שליחת בקשת HTTP לנקודת הקצה של OAuth userinfo ‏(https://www.googleapis.com/oauth2/v3/userinfo) באמצעות ספריית ה-HTTP המועדפת, וכוללים בכותרת את אסימון הגישה שקיבלתם, בדומה לפקודה curl הבאה:GET

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

    התגובה היא UserInfo, מוגבלת להיקפי ההרשאות שנדרשו, בפורמט JSON.

הרשאה מחשבון שלא מוגדר כברירת מחדל

אם אתם משתמשים במנהל האישורים לאימות ומריצים את AuthorizationClient.authorize(), חשבון ברירת המחדל של האפליקציה מוגדר לחשבון שנבחר על ידי המשתמש. המשמעות היא שכל קריאה עוקבת לאישור תשתמש בחשבון ברירת המחדל הזה. כדי להציג את בורר החשבונות, צריך להוציא את המשתמש מהאפליקציה באמצעות clearCredentialState() API מתוך 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.launchIntentSenderRequest.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
                }
            });
}