כניסה למשתמש באמצעות מנהל האישורים

Credential Manager הוא ממשק API של Jetpack שתומך בכמה שיטות כניסה, כמו שם משתמש וסיסמה, מפתחות גישה ופתרונות של כניסה מאוחדת (כמו 'כניסה באמצעות חשבון Google') בממשק API אחד, וכך מפשט את השילוב למפתחים.

בנוסף, Credential Manager מציע ממשק אחיד לכניסה לאפליקציות בכמה שיטות: 'כניסה באמצעות חשבון Google', מפתחות גישה וסיסמאות, שכולן יקלו על המשתמשים להיכנס לאפליקציות שונות.

בדף הזה מוסבר מהם מפתחות גישה ואיך מטמיעים תמיכה בצד הלקוח לפתרונות אימות, כולל מפתחות גישה, באמצעות Credential Manager API. יש גם דף שאלות נפוצות נפרד עם תשובות לשאלות ספציפיות ומפורטות יותר.

המשוב שלכם הוא חלק חיוני בשיפור ה-Credential Manager API. אתם יכולים לשלוח לנו בעיות שמצאתם או רעיונות לשיפור ה-API באמצעות הקישור הבא:

שליחת משוב

מידע על מפתחות גישה

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

מפתחות הגישה מסתמכים על WebAuthn (אימות באינטרנט), תקן שפותח במשותף על ידי FIDO Alliance ו-World Wide Web Consortium (W3C). ב-WebAuthn נעשה שימוש בקריפטוגרפיה של מפתח ציבורי כדי לאמת את המשתמש. האתר או האפליקציה שבהם המשתמש נכנס לחשבון יכולים לראות ולאחסן את המפתח הציבורי, אבל אף פעם לא את המפתח הפרטי. המפתח הפרטי נשמר בסודיות ובבטחה. בנוסף, מכיוון שהמפתח ייחודי ומקושר לאתר או לאפליקציה, אי אפשר לפרוץ למפתחות גישה, וכך הם מספקים עוד שכבת אבטחה.

Credential Manager מאפשר למשתמשים ליצור מפתחות גישה ולאחסן אותם במנהל הסיסמאות של Google.

במאמר אימות משתמשים באמצעות מפתחות גישה מוסבר איך מטמיעים תהליכי אימות חלקים באמצעות מפתחות גישה באמצעות Credential Manager.

דרישות מוקדמות

כדי להשתמש ב-Credential Manager, מבצעים את השלבים שמפורטים בקטע הזה.

שימוש בגרסה עדכנית של הפלטפורמה

יש תמיכה ב-Credential Manager ב-Android 4.4 (רמת API 19) ואילך.

הוספת יחסי תלות לאפליקציה

מוסיפים את יחסי התלות הבאים לסקריפט ה-build של מודול האפליקציה:

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.5.0-alpha05")

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation("androidx.credentials:credentials-play-services-auth:1.5.0-alpha05")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.5.0-alpha05"

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation "androidx.credentials:credentials-play-services-auth:1.5.0-alpha05"
}

שמירה של הכיתות בקובץ ProGuard

לקובץ proguard-rules.pro של המודול, מוסיפים את ההוראות הבאות:

-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
  *;
}
UnsupportedOperationException("Post-U not supported yet")

איך מצמצמים, מטשטשים ומבצעים אופטימיזציה של האפליקציה

הוספת תמיכה בקישורים לנכסים דיגיטליים

כדי להפעיל תמיכה במפתחות גישה לאפליקציה ל-Android, משייכים את האפליקציה לאתר שבבעלות האפליקציה. כדי להצהיר על השיוך הזה, מבצעים את השלבים הבאים:

  1. יוצרים קובץ JSON של Digital Asset Links. לדוגמה, כדי להצהיר שהאתר https://signin.example.com ואפליקציה ל-Android עם שם החבילה com.example יכולים לשתף פרטי כניסה, יוצרים קובץ בשם assetlinks.json עם התוכן הבא:

    [
      {
        "relation" : [
          "delegate_permission/common.handle_all_urls",
          "delegate_permission/common.get_login_creds"
        ],
        "target" : {
          "namespace" : "android_app",
          "package_name" : "com.example.android",
          "sha256_cert_fingerprints" : [
            SHA_HEX_VALUE
          ]
        }
      }
    ]
    

    השדה relation הוא מערך של מחרוזת אחת או יותר שמתארות את הקשר שמוצהר. כדי להצהיר על כך שאפליקציות ואתרים משתפים פרטי כניסה, מציינים את היחסים בתור delegate_permission/handle_all_urls ו-delegate_permission/common.get_login_creds.

    השדה target הוא אובייקט שמציין את הנכס שאליו ההצהרה רלוונטית. השדות הבאים מזהים אתר:

    namespace web
    site

    כתובת ה-URL של האתר, בפורמט https://domain[:optional_port]. לדוגמה, https://www.example.com.

    השדה domain חייב להיות מוגדר במלואו, ו-optional_port חייב להימחק כשמשתמשים ביציאה 443 ל-HTTPS.

    יעד site יכול להיות רק דומיין ברמה הבסיסית: אי אפשר להגביל את השיוך של האפליקציה לתיקיית משנה ספציפית. חשוב לא לכלול נתיב בכתובת ה-URL, כמו קו נטוי בסוף.

    תת-דומיינים לא נחשבים כתואם: כלומר, אם מציינים את domain בתור www.example.com, הדומיין www.counter.example.com לא משויך לאפליקציה.

    השדות הבאים מזהים אפליקציה ל-Android:

    namespace android_app
    package_name שם החבילה שצוין במניפסט של האפליקציה. לדוגמה, com.example.android
    sha256_cert_fingerprints טביעות האצבע מסוג SHA256 של אישור החתימה של האפליקציה.
  2. מארחים את קובץ ה-JSON של Digital Asset Links במיקום הבא בדומיין הכניסה:

    https://domain[:optional_port]/.well-known/assetlinks.json
    

    לדוגמה, אם הדומיין של כניסה הוא signin.example.com, מארחים את קובץ ה-JSON בכתובת https://signin.example.com/.well-known/assetlinks.json.

    סוג ה-MIME של קובץ Digital Asset Links צריך להיות JSON. חשוב לוודא שהשרת שולח כותרת Content-Type: application/json בתגובה.

  3. אתם צריכים לוודא שהמארח מאפשר ל-Google לאחזר את קובץ ה-Digital Asset Link. אם יש לכם קובץ robots.txt, הוא חייב לאפשר לסוכן Googlebot לאחזר את /.well-known/assetlinks.json. ברוב האתרים אפשר לאפשר לכל סוכן אוטומטי לאחזר קבצים בנתיב /.well-known/, כדי ששירותים אחרים יוכלו לגשת למטא-נתונים בקבצים האלה:

    User-agent: *
    Allow: /.well-known/
    
  4. מוסיפים את השורה הבאה לקובץ המניפסט דרך <application>:

    <meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
    
  5. אם אתם משתמשים בכניסה באמצעות סיסמה דרך Credential Manager, עליכם לפעול לפי השלב הזה כדי להגדיר קישור של נכסים דיגיטליים במניפסט. אין צורך לבצע את השלב הזה אם משתמשים רק במפתחות גישה.

    מגדירים את השיוך באפליקציה ל-Android. מוסיפים אובייקט שמציין את קובצי assetlinks.json שרוצים לטעון. צריך לסמן בתו בריחה (escape) את כל הגרשיים והמירכאות שבשרשור. לדוגמה:

    <string name="asset_statements" translatable="false">
    [{
      \"include\": \"https://signin.example.com/.well-known/assetlinks.json\"
    }]
    </string>
    
    > GET /.well-known/assetlinks.json HTTP/1.1
    > User-Agent: curl/7.35.0
    > Host: signin.example.com
    
    < HTTP/1.1 200 OK
    < Content-Type: application/json
    

הגדרה של 'מנהל פרטי הכניסה'

כדי להגדיר ולאתחל אובייקט CredentialManager, מוסיפים לוגיקה דומה לזו:

Kotlin

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
val credentialManager = CredentialManager.create(context)

Java

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
CredentialManager credentialManager = CredentialManager.create(context)

סימון שדות פרטי הכניסה

ב-Android מגרסה 14 ואילך, אפשר להשתמש במאפיין isCredential כדי לציין שדות של פרטי כניסה, כמו שדות של שם משתמש או סיסמה. המאפיין הזה מציין שהתצוגה הזו היא שדה פרטי כניסה שנועד לפעול עם מנהל פרטי הכניסה ועם ספקי פרטי כניסה של צד שלישי, ועוזרת לשירותי מילוי אוטומטי לספק הצעות טובות יותר למילוי אוטומטי. כשהאפליקציה משתמשת ב-Credential Manager API, מוצגת הגיליון התחתון של Credential Manager עם פרטי הכניסה הזמינים, ואין צורך להציג את תיבת הדו-שיח של המילוי האוטומטי בשביל שם המשתמש או הסיסמה. באופן דומה, אין צורך להציג את תיבת הדו-שיח של שמירת הסיסמאות של המילוי האוטומטי, כי האפליקציה תבקש מ-Credential Manager API לשמור את פרטי הכניסה.

כדי להשתמש במאפיין isCredential, מוסיפים אותו לתצוגות הרלוונטיות:

<TextView
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:isCredential="true"
...
 />

כניסה של המשתמש

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

  1. מאתחלים את אפשרויות האימות באמצעות סיסמה ומפתח גישה:

    Kotlin

    // Retrieves the user's saved password for your app from their
    // password provider.
    val getPasswordOption = GetPasswordOption()
    
    // Get passkey from the user's public key credential provider.
    val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
        requestJson = requestJson
    )

    Java

    // Retrieves the user's saved password for your app from their
    // password provider.
    GetPasswordOption getPasswordOption = new GetPasswordOption();
    
    // Get passkey from the user's public key credential provider.
    GetPublicKeyCredentialOption getPublicKeyCredentialOption =
            new GetPublicKeyCredentialOption(requestJson);
  2. כדי ליצור את בקשת הכניסה צריך להשתמש באפשרויות שאוחזרו מהשלב הקודם.

    Kotlin

    val getCredRequest = GetCredentialRequest(
        listOf(getPasswordOption, getPublicKeyCredentialOption)
    )

    Java

    GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder()
        .addCredentialOption(getPasswordOption)
        .addCredentialOption(getPublicKeyCredentialOption)
        .build();
  3. מפעילים את תהליך הכניסה:

    Kotlin

    coroutineScope.launch {
        try {
            val result = credentialManager.getCredential(
                // Use an activity-based context to avoid undefined system UI
                // launching behavior.
                context = activityContext,
                request = getCredRequest
            )
            handleSignIn(result)
        } catch (e : GetCredentialException) {
            handleFailure(e)
        }
    }
    
    fun handleSignIn(result: GetCredentialResponse) {
        // Handle the successfully returned credential.
        val credential = result.credential
    
        when (credential) {
            is PublicKeyCredential -> {
                val responseJson = credential.authenticationResponseJson
                // Share responseJson i.e. a GetCredentialResponse on your server to
                // validate and  authenticate
            }
            is PasswordCredential -> {
                val username = credential.id
                val password = credential.password
                // Use id and password to send to your server to validate
                // and authenticate
            }
          is CustomCredential -> {
              // If you are also using any external sign-in libraries, parse them
              // here with the utility functions provided.
              if (credential.type == ExampleCustomCredential.TYPE)  {
              try {
                  val ExampleCustomCredential = ExampleCustomCredential.createFrom(credential.data)
                  // Extract the required credentials and complete the authentication as per
                  // the federated sign in or any external sign in library flow
                  } catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) {
                      // Unlikely to happen. If it does, you likely need to update the dependency
                      // version of your external sign-in library.
                      Log.e(TAG, "Failed to parse an ExampleCustomCredential", e)
                  }
              } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential")
              }
            } else -> {
                // Catch any unrecognized credential type here.
                Log.e(TAG, "Unexpected type of credential")
            }
        }
    }

    Java

    credentialManager.getCredentialAsync(
        // Use activity based context to avoid undefined
        // system UI launching behavior
        activity,
        getCredRequest,
        cancellationSignal,
        <executor>,
        new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
            @Override
            public void onResult(GetCredentialResponse result) {
                handleSignIn(result);
            }
    
            @Override
            public void onError(GetCredentialException e) {
                handleFailure(e);
            }
        }
    );
    
    public void handleSignIn(GetCredentialResponse result) {
        // Handle the successfully returned credential.
        Credential credential = result.getCredential();
        if (credential instanceof PublicKeyCredential) {
            String responseJson = ((PublicKeyCredential) credential).getAuthenticationResponseJson();
            // Share responseJson i.e. a GetCredentialResponse on your server to validate and authenticate
        } else if (credential instanceof PasswordCredential) {
            String username = ((PasswordCredential) credential).getId();
            String password = ((PasswordCredential) credential).getPassword();
            // Use id and password to send to your server to validate and authenticate
        } else if (credential instanceof CustomCredential) {
            if (ExampleCustomCredential.TYPE.equals(credential.getType())) {
                try {
                    ExampleCustomCredential customCred = ExampleCustomCredential.createFrom(customCredential.getData());
                    // Extract the required credentials and complete the
                    // authentication as per the federated sign in or any external
                    // sign in library flow
                } catch (ExampleCustomCredential.ExampleCustomCredentialParsingException e) {
                    // Unlikely to happen. If it does, you likely need to update the
                    // dependency version of your external sign-in library.
                    Log.e(TAG, "Failed to parse an ExampleCustomCredential", e);
                }
            } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential");
            }
        } else {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential");
        }
    }

בדוגמה הבאה מוסבר איך לעצב את בקשת ה-JSON כשמקבלים מפתח גישה:

{
  "challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
  "allowCredentials": [],
  "timeout": 1800000,
  "userVerification": "required",
  "rpId": "credential-manager-app-test.glitch.me"
}

בדוגמה הבאה אפשר לראות איך עשויה להיראות תגובת JSON אחרי שמקבלים פרטי כניסה למפתח ציבורי:

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
    "signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
    "userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
  }
}

טיפול בחריגות כשאין פרטי כניסה זמינים

במקרים מסוימים, יכול להיות שלמשתמש אין פרטי כניסה זמינים או שהמשתמש לא מסכים להשתמש בפרטי כניסה זמינים. אם מפעילים את הפקודה getCredential() ולא נמצאו פרטי כניסה, מוחזר NoCredentialException. במקרה כזה, הקוד צריך לטפל במופעים של NoCredentialException.

Kotlin

try {
  val credential = credentialManager.getCredential(credentialRequest)
} catch (e: NoCredentialException) {
  Log.e("CredentialManager", "No credential available", e)
}

Java

try {
  Credential credential = credentialManager.getCredential(credentialRequest);
} catch (NoCredentialException e) {
  Log.e("CredentialManager", "No credential available", e);
}

ב-Android 14 ואילך, אפשר להשתמש בשיטה prepareGetCredential() לפני שמפעילים את getCredential() כדי לקצר את זמן האחזור כשמציגים את בורר החשבונות.

Kotlin

val response = credentialManager.prepareGetCredential(
  GetCredentialRequest(
    listOf(
      <getPublicKeyCredentialOption>,
      <getPasswordOption>
    )
  )
}

Java

GetCredentialResponse response = credentialManager.prepareGetCredential(
  new GetCredentialRequest(
    Arrays.asList(
      new PublicKeyCredentialOption(),
      new PasswordOption()
    )
  )
);

השיטה prepareGetCredential() לא מפעילה רכיבי ממשק משתמש. הוא רק עוזר לכם לבצע את עבודת ההכנה כדי שתוכלו להפעיל מאוחר יותר את שאר הפעולות של get-credential (שכוללות ממשקי משתמש) דרך ה-API של getCredential().

הנתונים שנשמרו במטמון מוחזרים באובייקט PrepareGetCredentialResponse. אם יש פרטי כניסה קיימים, התוצאות יישמרו במטמון ותוכלו להפעיל מאוחר יותר את שאר ממשקי ה-API של getCredential() כדי להציג את בורר החשבונות עם הנתונים ששמורים במטמון.

תהליכי רישום

אפשר לרשום משתמש לאימות באמצעות מפתח גישה או סיסמה.

יצירת מפתח גישה

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

Kotlin

fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) {
    val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(
        // Contains the request in JSON format. Uses the standard WebAuthn
        // web JSON spec.
        requestJson = requestJson,
        // Defines whether you prefer to use only immediately available
        // credentials, not hybrid credentials, to fulfill this request.
        // This value is false by default.
        preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
    )

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    coroutineScope.launch {
        try {
            val result = credentialManager.createCredential(
                // Use an activity-based context to avoid undefined system
                // UI launching behavior
                context = activityContext,
                request = createPublicKeyCredentialRequest,
            )
            handlePasskeyRegistrationResult(result)
        } catch (e : CreateCredentialException){
            handleFailure(e)
        }
    }
}

fun handleFailure(e: CreateCredentialException) {
    when (e) {
        is CreatePublicKeyCredentialDomException -> {
            // Handle the passkey DOM errors thrown according to the
            // WebAuthn spec.
            handlePasskeyError(e.domError)
        }
        is CreateCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to register the credential.
        }
        is CreateCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is CreateCredentialProviderConfigurationException -> {
            // Your app is missing the provider configuration dependency.
            // Most likely, you're missing the
            // "credentials-play-services-auth" module.
        }
        is CreateCredentialUnknownException -> ...
        is CreateCredentialCustomException -> {
            // You have encountered an error from a 3rd-party SDK. If you
            // make the API call with a request object that's a subclass of
            // CreateCustomCredentialRequest using a 3rd-party SDK, then you
            // should check for any custom exception type constants within
            // that SDK to match with e.type. Otherwise, drop or log the
            // exception.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
    }
}

Java

public void createPasskey(String requestJson, boolean preferImmediatelyAvailableCredentials) {
    CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest =
            // `requestJson` contains the request in JSON format. Uses the standard
            // WebAuthn web JSON spec.
            // `preferImmediatelyAvailableCredentials` defines whether you prefer
            // to only use immediately available credentials, not  hybrid credentials,
            // to fulfill this request. This value is false by default.
            new CreatePublicKeyCredentialRequest(
                requestJson, preferImmediatelyAvailableCredentials);

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined system
        // UI launching behavior
        requireActivity(),
        createPublicKeyCredentialRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleSuccessfulCreatePasskeyResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                if (e instanceof CreatePublicKeyCredentialDomException) {
                    // Handle the passkey DOM errors thrown according to the
                    // WebAuthn spec.
                    handlePasskeyError(((CreatePublicKeyCredentialDomException)e).getDomError());
                } else if (e instanceof CreateCredentialCancellationException) {
                    // The user intentionally canceled the operation and chose not
                    // to register the credential.
                } else if (e instanceof CreateCredentialInterruptedException) {
                    // Retry-able error. Consider retrying the call.
                } else if (e instanceof CreateCredentialProviderConfigurationException) {
                    // Your app is missing the provider configuration dependency.
                    // Most likely, you're missing the
                    // "credentials-play-services-auth" module.
                } else if (e instanceof CreateCredentialUnknownException) {
                } else if (e instanceof CreateCredentialCustomException) {
                    // You have encountered an error from a 3rd-party SDK. If
                    // you make the API call with a request object that's a
                    // subclass of
                    // CreateCustomCredentialRequest using a 3rd-party SDK,
                    // then you should check for any custom exception type
                    // constants within that SDK to match with e.type.
                    // Otherwise, drop or log the exception.
                } else {
                  Log.w(TAG, "Unexpected exception type "
                          + e.getClass().getName());
                }
            }
        }
    );
}

הפורמט של בקשת ה-JSON

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

בפוסט הזה בבלוג על הוספת אימות חלק לאפליקציות מוסבר איך לעצב את בקשת ה-JSON כשיוצרים מפתחות גישה וכשיוצרים אימות באמצעות מפתחות גישה. בנוסף, מוסבר למה סיסמאות הן לא פתרון אימות יעיל, איך להשתמש בפרטי כניסה ביומטריים קיימים, איך לשייך את האפליקציה לאתר שבבעלותכם, איך ליצור מפתחות גישה ואיך לבצע אימות באמצעות מפתחות גישה.

{
  "challenge": "abc123",
  "rp": {
    "name": "Credential Manager example",
    "id": "credential-manager-test.example.com"
  },
  "user": {
    "id": "def456",
    "name": "helloandroid@gmail.com",
    "displayName": "helloandroid@gmail.com"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {"id": "ghi789", "type": "public-key"},
    {"id": "jkl012", "type": "public-key"}
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

הגדרת ערכים ל-authenticatorAttachment

אפשר להגדיר את הפרמטר authenticatorAttachment רק בזמן היצירה של פרטי הכניסה. אפשר לציין platform, cross-platform או לא להגדיר ערך בכלל. ברוב המקרים לא מומלץ להגדיר ערך.

  • platform: כדי לרשום את המכשיר הנוכחי של המשתמש או להציג למשתמש שמשתמש בסיסמה בקשה לשדרג למפתחות גישה אחרי כניסה לחשבון, מגדירים את authenticatorAttachment לערך platform.
  • cross-platform: הערך הזה משמש בדרך כלל כשרוצים להירשם לפרטי כניסה עם אימות רב-גורמי, ולא משמש בהקשר של מפתח גישה.
  • ללא ערך: כדי לתת למשתמשים גמישות ליצור מפתחות גישה במכשירים המועדפים עליהם (למשל, בהגדרות החשבון), לא צריך לציין את הפרמטר authenticatorAttachment כשמשתמש בוחר להוסיף מפתח גישה. ברוב המקרים, האפשרות הטובה ביותר היא להשאיר את הפרמטר ללא ציון.

מניעת יצירה של מפתחות גישה כפולים

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

טיפול בתגובת ה-JSON

בקטע הקוד הבא מוצגת תגובת JSON לדוגמה ליצירת פרטי כניסה למפתח ציבורי. מידע נוסף על טיפול בפרטי הכניסה של המפתח הציבורי שהוחזרו

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
  }
}

אימות המקור מקובץ JSON של נתוני לקוח

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

במקרה באינטרנט, origin משקף את אותו מקור באותו אתר שבו פרטי הכניסה נכנסו. לדוגמה, אם כתובת ה-URL היא https://www.example.com:8443/store?category=shoes#athletic , הערך של origin הוא https://www.example.com:8443.

באפליקציות ל-Android, סוכן המשתמש מגדיר את origin באופן אוטומטי לחתימה של האפליקציה הקוראת. צריך לאמת את החתימה הזו כחתימה תואמת בשרת כדי לאמת את מבצע הקריאה של Passkey API. הערך של Android origin הוא URI שמבוסס על הגיבוב (hash) של SHA-256 של אישור החתימה של קובץ ה-APK, למשל:

android:apk-key-hash:<sha256_hash-of-apk-signing-cert>

כדי למצוא את הגיבובים מסוג SHA-256 של אישורי החתימה מאחסון המפתחות, מריצים את הפקודה הבאה במסוף:

keytool -list -keystore <path-to-apk-signing-keystore>

הגיבובים מסוג SHA-256 מופיעים בפורמט הקסדצימלי שמופרד בנקודתיים (91:F7:CB:F9:D6:81…), וערכי origin ב-Android מקודדים ב-base64url. בדוגמה הבאה ב-Python מוסבר איך להמיר את פורמט ה-hash לפורמט הקסדצימלי תואם שמופרד בפסיקים:

import binascii
import base64
fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5'
print("android:apk-key-hash:" + base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))

מחליפים את הערך של fingerprint בערך משלכם. דוגמה לתוצאה:

android:apk-key-hash:kffL-daBUxvHpY-4M8yhTavt5QnFEI2LsexohxrGPYU

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

שמירת סיסמה של משתמש

אם המשתמש מספק שם משתמש וסיסמה לתהליך אימות באפליקציה, תוכלו לרשום פרטי כניסה של משתמש שבאמצעותם אפשר לאמת את המשתמש. כדי לעשות זאת, יוצרים אובייקט CreatePasswordRequest:

Kotlin

fun registerPassword(username: String, password: String) {
    // Initialize a CreatePasswordRequest object.
    val createPasswordRequest =
            CreatePasswordRequest(id = username, password = password)

    // Create credential and handle result.
    coroutineScope.launch {
        try {
            val result =
                credentialManager.createCredential(
                    // Use an activity based context to avoid undefined
                    // system UI launching behavior.
                    activityContext,
                    createPasswordRequest
                  )
            handleRegisterPasswordResult(result)
        } catch (e: CreateCredentialException) {
            handleFailure(e)
        }
    }
}

Java

void registerPassword(String username, String password) {
    // Initialize a CreatePasswordRequest object.
    CreatePasswordRequest createPasswordRequest =
        new CreatePasswordRequest(username, password);

    // Register the username and password.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined
        // system UI launching behavior
        requireActivity(),
        createPasswordRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                handleFailure(e);
            }
        }
    );
}

תמיכה בשחזור פרטי כניסה

אם למשתמש כבר אין גישה למכשיר שבו אחסנו את פרטי הכניסה, יכול להיות שהוא יצטרך לשחזר נתונים מגיבוי מאובטח אונליין. מידע נוסף על תמיכה בתהליך שחזור פרטי הכניסה זמין בקטע 'שחזור הגישה או הוספת מכשירים חדשים' בפוסט הזה בבלוג: אבטחת מפתחות הגישה במנהל הסיסמאות של Google.

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

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

  1. אם צד נסמך ב-https://example.com, שיש לו אתר וגם אפליקציות ל-Android ול-iOS, כתובת ה-URL המוכרת תהיה https://example.com/.well-known/passkey-endpoints.
  2. כששולחים שאילתה לגבי כתובת ה-URL, התגובה צריכה להשתמש בסכימה הבאה

    {
      "enroll": "https://example.com/account/manage/passkeys/create"
      "manage": "https://example.com/account/manage/passkeys"
    }
    
  3. כדי שהקישור הזה ייפתח ישירות באפליקציה במקום באינטרנט, צריך להשתמש בקישורים לאפליקציות ל-Android.

  4. פרטים נוספים זמינים במאמר ההסבר על כתובת URL ידועה של נקודות קצה למפתחות גישה ב-GitHub.

אפשר לעזור למשתמשים לנהל את מפתחות הגישה שלהם על ידי הצגת הספק שיצר אותם

אחד האתגרים שמשתמשים נתקלים בו כשהם מנהלים כמה מפתחות גישה שמשויכים לאפליקציה מסוימת הוא זיהוי מפתח הגישה הנכון לצורך עריכה או מחיקה. כדי לעזור לפתור את הבעיה הזו, מומלץ שרשימה של מפתחות הגישה במסך ההגדרות של האפליקציה תכלול מידע נוסף, כמו הספק שיצר את פרטי הכניסה, תאריך היצירה ותאריך השימוש האחרון.כדי לקבל את פרטי הספק, בודקים את הערך של AAGUID שמשויך למפתח הגישה המתאים. ה-AAGUID נמצא כחלק מנתוני האימות של מפתח הגישה.

לדוגמה, אם משתמש יוצר מפתח גישה במכשיר Android באמצעות 'מנהל הסיסמאות של Google', ה-RP מקבל AAGUID שנראה כך: "ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4". הצד הנסמך יכול להוסיף הערה למפתח הגישה ברשימת מפתחות הגישה כדי לציין שהוא נוצר באמצעות מנהל הסיסמאות של Google.

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

מידע נוסף על שילוב AAGUID

פתרון בעיות נפוצות

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

קוד השגיאה והתיאור שלה הסיבה
ב-Begin Sign In Failure: ‏ 16: מבצע הקריאה חוסם באופן זמני בגלל יותר מדי הנחיות כניסה שבוטלו.

אם נתקלתם בתקופה של 24 שעות לזמן קירור במהלך הפיתוח, תוכלו לאפס אותה על ידי ניקוי האחסון של אפליקציית Google Play Services.

לחלופין, כדי להפעיל או להשבית את תקופת הצינון הזו במכשיר בדיקה או במהדמ, עוברים לאפליקציית Dialer ומזינים את הקוד הבא: *#*#66382723#*#*. כל הקלט באפליקציית החיוג יימחק, והיא עשויה להיסגר, אבל לא תוצג הודעה לאישור.

כשהכניסה נכשלת בשלב 'התחלה': 8: שגיאה פנימית לא ידועה.
  1. המכשיר לא מוגדר כראוי עם חשבון Google.
  2. קובץ ה-JSON של מפתח הגישה נוצר בצורה שגויה.
CreatePublicKeyCredentialDomException: The incoming request cannot be validated מזהה החבילה של האפליקציה לא רשום בשרת. מוודאים זאת בשילוב בצד השרת.
Create CredentialsUnknownError: במהלך שמירת הסיסמה נמצאה תגובה של כשל בסיסמה בהקשה אחת 16: דילוג על שמירת הסיסמאות כי סביר להניח שהמשתמש מתבקש למלא את המילוי האוטומטי של Android השגיאה הזו מתרחשת רק ב-Android מגרסה 13 ומטה, ורק אם Google היא ספקית המילוי האוטומטי. במקרה כזה, המשתמשים יראו בקשה לשמירה מהמילוי האוטומטי והסיסמה תישמר במנהל הסיסמאות של Google. שימו לב שפרטי הכניסה שנשמרו באמצעות המילוי האוטומטי של Google משותפים באופן דו-כיווני עם Credential Manager API. לכן אפשר להתעלם מהשגיאה הזו.

מקורות מידע נוספים

למידע נוסף על Credential Manager API ומפתחות גישה, כדאי לעיין במקורות המידע הבאים: