אימות באמצעות Google Play Games Services עם C++

אתם יכולים להשתמש בשירותי המשחקים של Google Play כדי להכניס שחקנים למשחק ל-Android שמבוסס על Firebase ונכתב ב-C++. כדי להשתמש בכניסה לשירותי המשחקים של Google Play עם Firebase, קודם צריך להכניס את השחקן באמצעות שירותי המשחקים של Google Play ולבקש קוד הרשאה של OAuth 2.0. אחר כך, מעבירים את קוד ההרשאה אל PlayGamesAuthProvider כדי ליצור פרטי כניסה ל-Firebase, שאפשר להשתמש בהם כדי לבצע אימות ב-Firebase.

לפני שמתחילים

כדי להשתמש ב-Firebase Authentication, צריך:

  • רושמים את פרויקט C++‎ ומגדירים אותו לשימוש ב-Firebase.

    אם פרויקט C++ שלכם כבר משתמש ב-Firebase, הוא כבר רשום ומותאם ל-Firebase.

  • מוסיפים את Firebase C++ SDK לפרויקט C++.

שימו לב שהוספת Firebase לפרויקט C++‎ כוללת משימות במסוף Firebase ובפרויקט C++‎ הפתוח (לדוגמה, הורדה של קובצי תצורה של Firebase מהמסוף והעברה שלהם לפרויקט C++‎).

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

  1. אם עדיין לא עשיתם זאת, מציינים את טביעת האצבע מסוג SHA-1 של האפליקציה.

    1. במסוף Firebase, עוברים אל הגדרות > הכרטיסייה כללי.

    2. גוללים למטה אל הכרטיס Your apps, בוחרים את האפליקציה ל-Android ומוסיפים את טביעת האצבע מסוג SHA-1 בשדה SHA certificate fingerprints.

    אפשר לקבל את הגיבוב SHA של אישור החתימה באמצעות הפקודה gradle signingReport:

    ./gradlew signingReport

    במאמר אימות הלקוח מוסבר איך מקבלים את טביעת האצבע של ה-SHA של האפליקציה.

  2. הפעלת Google Play Games כספק כניסה:

    1. במסוף Firebase, עוברים אל Security (אבטחה) >‏ Authentication (אימות).

    2. יוצרים ומקבלים את מזהה הלקוח של שרת האינטרנט של הפרויקט ואת סוד הלקוח:

      1. בכרטיסייה Sign in method (שיטת הכניסה), מפעילים את ספק הכניסה Google.

      2. מעתיקים את מזהה הלקוח ואת הסוד של שרת האינטרנט מספק הכניסה Google.

    3. בכרטיסייה Sign in method (שיטת הכניסה), מפעילים את ספק הכניסה Play Games ומציינים את מזהה הלקוח (Client ID) ואת סוד הלקוח (Client Secret) של שרת האינטרנט של הפרויקט, שקיבלתם בשלב הקודם.

מגדירים את Play Games services עם פרטי האפליקציה ב-Firebase

  1. במסוף Google Play, פותחים את אפליקציית Google Play או יוצרים אחת.

  2. בקטע צמיחה, לוחצים על Play Games services > הגדרה וניהול > הגדרות.

  3. לוחצים על כן, המשחק שלי כבר עושה שימוש ב-Google APIs, בוחרים את הפרויקט ב-Firebase מהרשימה ולוחצים על שימוש.

  4. בדף ההגדרה של Play Games services, לוחצים על הוספת פרטי כניסה.

    1. בוחרים בסוג שרת גיימינג.
    2. בשדה לקוח OAuth, בוחרים את מזהה לקוח האינטרנט של הפרויקט. חשוב לוודא שזהו אותו מספר לקוח שציינתם כשפעלתם את הכניסה לחשבון Play Games.
    3. שומרים את השינויים.
  5. עדיין בדף ההגדרה של Play Games services, לוחצים שוב על Add Credential.

    1. בוחרים בסוג Android.
    2. בשדה לקוח OAuth, בוחרים את מזהה הלקוח של Android בפרויקט. (אם מזהה הלקוח של Android לא מופיע, צריך לוודא שהגדרתם את טביעת האצבע של SHA-1 של המשחק בFirebase).
    3. שומרים את השינויים.
  6. בדף בודקים, מוסיפים את כתובות האימייל של כל המשתמשים שצריכים להיות מסוגלים להיכנס למשחק לפני שמפרסמים אותו ב-Play Store.

שילוב של כניסה ל-Play Games במשחק

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

הדרך הקלה והמומלצת להוסיף תמיכה בכניסה ל-Play Games לפרויקט Android ב-C++‎ היא באמצעות Google Sign-in C++ SDK.

כדי להוסיף כניסה ל-Play Games למשחק באמצעות Google Sign-in C++ SDK, מבצעים את הפעולות הבאות:

  1. משכפלים או מורידים את מאגר הפלאגין של Google Sign-in Unity, שמכיל גם את ה-SDK בשפת C++‎.

  2. מבצעים build לפרויקט שנמצא בספרייה staging/native/, באמצעות Android Studio או gradlew build.

    ה-build מעתיק את הפלט שלו לספרייה בשם google-signin-cpp.

  3. כוללים את Google Sign-in C++ SDK בקובץ ה-make של קוד Native של המשחק:

    CMake

    בקובץ CMakeLists.txt ברמה העליונה:

    set(GSI_PACKAGE_DIR "/path/to/google-signin-cpp")
    add_library(lib-google-signin-cpp STATIC IMPORTED) set_target_properties(lib-google-signin-cpp PROPERTIES IMPORTED_LOCATION     ${GSI_PACKAGE_DIR}/lib/${ANDROID_ABI}/libgoogle-signin-cpp.a )
    ...
    target_link_libraries(     ...     lib-google-signin-cpp)

    ndk-build

    בקובץ Android.mk:

    include $(CLEAR_VARS)
    LOCAL_MODULE := google-signin-cpp
    GSI_SDK_DIR := /path/to/google-signin-cpp
    LOCAL_SRC_FILES := $(GSI_SDK_DIR)/lib/$(TARGET_ARCH_ABI)/libgoogle-signin-cpp.a
    LOCAL_EXPORT_C_INCLUDES := $(GSI_SDK_DIR)/include
    include $(PREBUILT_STATIC_LIBRARY)

  4. לאחר מכן, כוללים את רכיב העזר של Java, שנדרש על ידי C++ SDK.

    כדי לעשות את זה, מוסיפים את ספריית הפלט של ה-SDK build כמאגר מקומי בקובץ build.gradle ברמת הפרויקט:

    allprojects {
        repositories {
            // ...
            flatDir {
                dirs 'path/to/google-signin-cpp'
            }
        }
    }
    

    בנוסף, בקובץ build.gradle ברמת המודול, צריך להצהיר על רכיב העזר כתלות:

    dependencies {
        implementation 'com.google.android.gms:play-services-auth:21.5.1'
        // Depend on the AAR built with the Google Sign-in SDK in order to add
        // the Java helper classes, which are used by the C++ library.
        compile(name:'google-signin-cpp-release', ext:'aar')
    }
    
  5. לאחר מכן, במשחק, מגדירים אובייקט GoogleSignIn כדי להשתמש בכניסה ל-Play Games ולאחזר קוד הרשאה לשרת:

    #include "google_signin.h"
    #include "future.h"
    
    using namespace google::signin;
    
    // ...
    
    GoogleSignIn::Configuration config = {};
    config.web_client_id = "YOUR_WEB_CLIENT_ID_HERE";
    config.request_id_token = false;
    config.use_game_signin = true;
    config.request_auth_code = true;
    
    GoogleSignIn gsi = GoogleSignIn(GetActivity(), GetJavaVM());
    gsi.Configure(config);
    
  6. לבסוף, מתקשרים אל SignIn() כדי להכניס את השחקן ל-Play Games:

    Future<GoogleSignIn::SignInResult> &future = gsi.SignIn();
    

    כש-Future שמוחזר על ידי SignIn() נפתר, אפשר לקבל את קוד האימות של השרת מהתוצאה:

    if (!future.Pending()) {
        const GoogleSignIn::StatusCode status =
                static_cast<GoogleSignIn::StatusCode>(future.Status());
        if (status == GoogleSignIn::kStatusCodeSuccess) {
            // Player successfully signed in to Google Play! Get auth code to
            //   pass to Firebase
            const GoogleSignIn::SignInResult result =
                    static_cast<GoogleSignIn::SignInResult>(future.Result());
            const char* server_auth_code = result.User.GetServerAuthCode();
        }
    }
    

אימות באמצעות Firebase

אחרי שהשחקן נכנס לחשבון באמצעות Play Games, אפשר להשתמש בקוד האימות כדי לבצע אימות ב-Firebase.

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

  2. לאחר מכן, מחליפים את קוד ההרשאה משירותי Play Games בפרטי כניסה ל-Firebase, ומשתמשים בפרטי הכניסה ל-Firebase כדי לאמת את השחקן:

    firebase::auth::Credential credential =
        firebase::auth::PlayGamesAuthProvider::GetCredential(server_auth_code);
    firebase::Future<firebase::auth::AuthResult> result =
        auth->SignInAndRetrieveDataWithCredential(credential);
    
  3. אם התוכנית שלכם כוללת לולאת עדכון שפועלת באופן קבוע (למשל 30 או 60 פעמים בשנייה), אתם יכולים לבדוק את התוצאות פעם אחת בכל עדכון באמצעות Auth::SignInAndRetrieveDataWithCredentialLastResult:

    firebase::Future<firebase::auth::AuthResult> result =
        auth->SignInAndRetrieveDataWithCredentialLastResult();
    if (result.status() == firebase::kFutureStatusComplete) {
      if (result.error() == firebase::auth::kAuthErrorNone) {
        firebase::auth::AuthResult auth_result = *result.result();
        printf("Sign in succeeded for `%s`\n",
               auth_result.user.display_name().c_str());
      } else {
        printf("Sign in failed with error '%s'\n", result.error_message());
      }
    }

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

רישום של התקשרות חזרה ב-Future

בתוכניות מסוימות יש פונקציות Update שמופעלות 30 או 60 פעמים בשנייה. לדוגמה, הרבה משחקים פועלים לפי המודל הזה. התוכניות האלה יכולות להפעיל את הפונקציות LastResult כדי לבצע סקר של שיחות אסינכרוניות. עם זאת, אם התוכנית שלכם מבוססת על אירועים, יכול להיות שתעדיפו לרשום פונקציות של קריאה חוזרת. פונקציית קריאה חוזרת מופעלת עם השלמת ה-Future.
void OnCreateCallback(const firebase::Future<firebase::auth::User*>& result,
                      void* user_data) {
  // The callback is called when the Future enters the `complete` state.
  assert(result.status() == firebase::kFutureStatusComplete);

  // Use `user_data` to pass-in program context, if you like.
  MyProgramContext* program_context = static_cast<MyProgramContext*>(user_data);

  // Important to handle both success and failure situations.
  if (result.error() == firebase::auth::kAuthErrorNone) {
    firebase::auth::User* user = *result.result();
    printf("Create user succeeded for email %s\n", user->email().c_str());

    // Perform other actions on User, if you like.
    firebase::auth::User::UserProfile profile;
    profile.display_name = program_context->display_name;
    user->UpdateUserProfile(profile);

  } else {
    printf("Created user failed with error '%s'\n", result.error_message());
  }
}

void CreateUser(firebase::auth::Auth* auth) {
  // Callbacks work the same for any firebase::Future.
  firebase::Future<firebase::auth::AuthResult> result =
      auth->CreateUserWithEmailAndPasswordLastResult();

  // `&my_program_context` is passed verbatim to OnCreateCallback().
  result.OnCompletion(OnCreateCallback, &my_program_context);
}
אם רוצים, פונקציית הקריאה החוזרת יכולה להיות גם למדה.
void CreateUserUsingLambda(firebase::auth::Auth* auth) {
  // Callbacks work the same for any firebase::Future.
  firebase::Future<firebase::auth::AuthResult> result =
      auth->CreateUserWithEmailAndPasswordLastResult();

  // The lambda has the same signature as the callback function.
  result.OnCompletion(
      [](const firebase::Future<firebase::auth::User*>& result,
         void* user_data) {
        // `user_data` is the same as &my_program_context, below.
        // Note that we can't capture this value in the [] because std::function
        // is not supported by our minimum compiler spec (which is pre C++11).
        MyProgramContext* program_context =
            static_cast<MyProgramContext*>(user_data);

        // Process create user result...
        (void)program_context;
      },
      &my_program_context);
}

השלבים הבאים

אחרי שהמשתמש נכנס לחשבון בפעם הראשונה, נוצר חשבון משתמש חדש שמקושר למזהה שלו ב-Play Games. החשבון החדש הזה נשמר כחלק מפרויקט Firebase, ואפשר להשתמש בו כדי לזהות משתמש בכל אפליקציה בפרויקט.

במשחק, אפשר לקבל את ה-UID של המשתמש ב-Firebase מהאובייקט firebase::auth::User:

firebase::auth::User user = auth->current_user();
if (user.is_valid()) {
  std::string playerName = user.displayName();

  // The user's ID, unique to the Firebase project.
  // Do NOT use this value to authenticate with your backend server,
  // if you have one. Use firebase::auth::User::Token() instead.
  std::string uid = user.uid();
}

בכללי האבטחה של מסד נתונים בזמן אמת ב-Firebase ו-Cloud Storage, אפשר לקבל את מזהה המשתמש הייחודי של המשתמש המחובר מהמשתנה auth ולהשתמש בו כדי לקבוע לאילו נתונים המשתמש יכול לגשת.

כדי לקבל את פרטי השחקן של משתמש ב-Play Games או לגשת לשירותי Play Games, צריך להשתמש בממשקי ה-API שמסופקים על ידי Google Play Games services C++ SDK.

כדי להוציא משתמש מהחשבון, קוראים ל-SignOut():

auth->SignOut();