OAuth 2.0

במסמך הזה מתוארים OAuth 2.0, הזמנים שבהם כדאי להשתמש בו, האופן שבו מקבלים מזהי לקוח והאופן שבו משתמשים בו עם ספריית הלקוח של Google API ל-‎ .NET.

פרוטוקול OAuth 2.0

OAuth 2.0 הוא פרוטוקול ההרשאה שבו נעשה שימוש ב-Google APIs. מומלץ להכיר את הפרוטוקול באמצעות הקישורים הבאים:

קבלת סודות ומזהי לקוחות

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

בכל אחד מקטע הקוד שמוצג (למעט קטע הקוד של חשבון השירות), צריך להוריד את הסוד של הלקוח ולאחסן אותו כ-client_secrets.json בפרויקט.

פרטי כניסה

פרטי הכניסה של המשתמש

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

UserCredential ו-AuthorizationCodeFlow מטפלים ב'רענון' האוטומטי של האסימון, כלומר קבלת אסימון גישה חדש. הפעולה הזו מתבצעת באמצעות אסימון רענון לטווח ארוך, שמקבלים יחד עם אסימון הגישה אם משתמשים במאפיין access_type=offline במהלך תהליך קוד ההרשאה.

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

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

  • FileDataStore ב-NET, האפשרות הזו מבטיחה שפרטי הכניסה יישארו בקובץ.

ServiceAccountCredential

ServiceAccountCredential דומה לפונקציה UserCredential, אבל יש לה מטרה אחרת. Google OAuth 2.0 תומך באינטראקציות בין שרתים, כמו אינטראקציות בין אפליקציית אינטרנט לבין Google Cloud Storage. האפליקציה המבקשת צריכה להוכיח את הזהות שלה כדי לקבל גישה ל-API, ומשתמש קצה לא צריך להיות מעורב. ServiceAccountCredential שומר מפתח פרטי, שמשמש לחתימה על בקשה לקבלת אסימון גישה חדש.

גם UserCredential וגם ServiceAccountCredential מטמיעים את IConfigurableHttpClientInitializer, כך שאפשר לרשום כל אחד מהם בתור:

  • טיפול בתגובה שנכשלה, כך שהוא ירענן את האסימון אם הוא יקבל קוד סטטוס HTTP‏ 401.
  • מנטרה, כדי ליירט את הכותרת Authorization בכל בקשה.

יישומים מותקנים

קוד לדוגמה באמצעות Books API:

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

using Google.Apis.Auth.OAuth2;
using Google.Apis.Books.v1;
using Google.Apis.Books.v1.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;

namespace Books.ListMyLibrary
{
    /// <summary>
    /// Sample which demonstrates how to use the Books API.
    /// https://developers.google.com/books/docs/v1/getting_started
    /// <summary>
    internal class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            Console.WriteLine("Books API Sample: List MyLibrary");
            Console.WriteLine("================================");
            try
            {
                new Program().Run().Wait();
            }
            catch (AggregateException ex)
            {
                foreach (var e in ex.InnerExceptions)
                {
                    Console.WriteLine("ERROR: " + e.Message);
                }
            }
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }

        private async Task Run()
        {
            UserCredential credential;
            using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
            {
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.Load(stream).Secrets,
                    new[] { BooksService.Scope.Books },
                    "user", CancellationToken.None, new FileDataStore("Books.ListMyLibrary"));
            }

            // Create the service.
            var service = new BooksService(new BaseClientService.Initializer()
                {
                    HttpClientInitializer = credential,
                    ApplicationName = "Books API Sample",
                });

            var bookshelves = await service.Mylibrary.Bookshelves.List().ExecuteAsync();
            ...
        }
    }
}
  
  • בקוד לדוגמה הזה, קריאה ל-method GoogleWebAuthorizationBroker.AuthorizeAsync יוצרת מכונה חדשה של UserCredential. השיטה הסטטית הזו מקבלת את הפרמטרים הבאים:

    • סוד הלקוח (או שידור לסוד הלקוח).
    • ההיקפים הנדרשים.
    • מזהה המשתמש.
    • אסימון הביטול לביטול פעולה.
    • מאגר נתונים אופציונלי. אם לא מציינים מאגר נתונים, ברירת המחדל היא FileDataStore עם תיקיית ברירת מחדל Google.Apis.Auth. התיקייה נוצרת ב-Environment.SpecialFolder.ApplicationData.
  • ה-UserCredential שמוחזר על ידי השיטה הזו מוגדר כ-HttpClientInitializer ב-BooksService (באמצעות המאפיין המפעיל). כפי שהוסבר קודם, UserCredential מטמיע מתקן לאתחול לקוח HTTP.

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

    credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
        new ClientSecrets
        {
            ClientId = "PUT_CLIENT_ID_HERE",
            ClientSecret = "PUT_CLIENT_SECRETS_HERE"
        },
        new[] { BooksService.Scope.Books },
        "user",
        CancellationToken.None,
        new FileDataStore("Books.ListMyLibrary"));
          

כדאי לעיין בדוגמה למודעות ספרים.

אפליקציות אינטרנט (ASP.NET Core 3)

ממשקי Google API תומכים ב- OAuth 2.0 לאפליקציות של שרת אינטרנט.

הספרייה Google.Apis.Auth.AspNetCore3 היא הספרייה המומלצת לשימוש ברוב התרחישים של OAuth 2.0 שמבוססים על Google באפליקציות ASP.NET Core 3. הוא מטמיע טיפול OpenIdConnect באימות שספציפי ל-Google. הוא תומך באימות מצטבר ומגדיר את ה-IGoogleAuthProvider הניתן להזרקה כדי לספק פרטי כניסה ל-Google שאפשר להשתמש בהם עם Google APIs.

בקטע הזה מוסבר איך להגדיר את Google.Apis.Auth.AspNetCore3 ולהשתמש בו. הקוד שמוצג כאן מבוסס על Google.Apis.Auth.AspNetCore3.IntegrationTests, אפליקציית ASP.NET Core 3 רגילה שפועלת באופן מלא.

אם אתם רוצים להשתמש במסמכי התיעוד האלה כמדריך, תצטרכו אפליקציית ASP.NET Core 3 משלכם ותצטרכו לבצע את השלבים הבאים כתנאי מקדים.

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

  • מתקינים את החבילה Google.Apis.Auth.AspNetCore3.
  • אנחנו משתמשים ב-Google Drive API, כך שתצטרכו להתקין גם את החבילה Google.Apis.Drive.v3.
  • יוצרים פרויקט חדש ב-Google Cloud, אם עדיין אין לכם פרויקט כזה. כדי לעשות זאת, פועלים לפי ההוראות האלה. זה יהיה הפרויקט שאליו תשויך האפליקציה.
  • חשוב להפעיל את Google Drive API. כדי להפעיל ממשקי API, פועלים לפי ההוראות האלה.
  • יוצרים פרטי כניסה להרשאה שיזהו את האפליקציה שלכם ב-Google. פועלים לפי ההוראות האלה כדי ליצור פרטי כניסה להרשאה ולהוריד את הקובץ client_secrets.json. שני רגעים מיוחדים:
    • חשוב לשים לב שהסוג של פרטי הכניסה חייב להיות Web application.
    • כדי להפעיל את האפליקציה הזו, ה-URI היחיד להפניה אוטומטית שצריך להוסיף הוא https://localhost:5001/signin-oidc.

הגדרת האפליקציה לשימוש ב-Google.Apis.Auth.AspNetCore3

Google.Apis.Auth.AspNetCore3 מוגדר בכיתה Startup או בחלופה דומה שאתם משתמשים בה. קטעי הקוד הבאים מופקים מ- Startup.cs בפרויקט Google.Apis.Auth.AspNetCore3.IntegrationTests.

  • מוסיפים את ההנחיה הבאה לקובץ Startup.cs.
    using Google.Apis.Auth.AspNetCore3;
  • בשיטה Startup.ConfigureServices מוסיפים את הקוד הבא, ומחליפים את התוויות של מזהה הלקוח וסוד הלקוח בערכים שמופיעים בקובץ client_secrets.json. אפשר לטעון את הערכים האלה ישירות מקובץ ה-JSON, או לאחסן אותם בכל דרך מאובטחת אחרת. כדאי לעיין בשיטה ClientInfo.Load בפרויקט Google.Apis.Auth.AspNetCore3.IntegrationTests כדי לראות דוגמה לאופן שבו אפשר לטעון את הערכים האלה ישירות מקובץ ה-JSON.
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        // This configures Google.Apis.Auth.AspNetCore3 for use in this app.
        services
            .AddAuthentication(o =>
            {
                // This forces challenge results to be handled by Google OpenID Handler, so there's no
                // need to add an AccountController that emits challenges for Login.
                o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
                // This forces forbid results to be handled by Google OpenID Handler, which checks if
                // extra scopes are required and does automatic incremental auth.
                o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
                // Default scheme that will handle everything else.
                // Once a user is authenticated, the OAuth2 token info is stored in cookies.
                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddCookie()
            .AddGoogleOpenIdConnect(options =>
            {
                options.ClientId = {YOUR_CLIENT_ID};
                options.ClientSecret = {YOUR_CLIENT_SECRET};
            });
    }
          
  • בשיטה Startup.Configure, חשוב להוסיף לצינור עיבוד הנתונים רכיבי תוכנה לעיבוד נתונים ביניים של אימות והרשאה של ASP.NET Core 3, וגם הפניות אוטומטיות ל-HTTPS:
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ...
        app.UseHttpsRedirection();
        ...
    
        app.UseAuthentication();
        app.UseAuthorization();
    
        ...
    }
          

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

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

  • המשתמש לא רק צריך להיות מאומת, אלא גם צריך להעניק לאפליקציה את ההיקף https://www.googleapis.com/auth/drive.readonly, שאותו מציינים באמצעות המאפיין GoogleScopedAuthorize.
  • אנחנו משתמשים במנגנון הסטנדרטי של ASP.NET Core 3 להזרקת יחסי תלות כדי לקבל את הערך של IGoogleAuthProvider, שמשמש אותנו לקבלת פרטי הכניסה של המשתמש.

הקוד:

  • קודם מוסיפים את ההוראות הבאות באמצעות ל-controller.
    using Google.Apis.Auth.AspNetCore3;
    using Google.Apis.Auth.OAuth2;
    using Google.Apis.Drive.v3;
    using Google.Apis.Services;
          
  • מוסיפים את הפעולה של הבקר, באופן הבא (ומצרפים לה תצוגה שמקבלת מודל IList<string>):
    /// <summary>
    /// Lists the authenticated user's Google Drive files.
    /// Specifying the <see cref="GoogleScopedAuthorizeAttribute"> will guarantee that the code
    /// executes only if the user is authenticated and has granted the scope specified in the attribute
    /// to this application.
    /// </summary>
    /// <param name="auth">The Google authorization provider.
    /// This can also be injected on the controller constructor.</param>
    [GoogleScopedAuthorize(DriveService.ScopeConstants.DriveReadonly)]
    public async Task<IActionResult> DriveFileList([FromServices] IGoogleAuthProvider auth)
    {
        GoogleCredential cred = await auth.GetCredentialAsync();
        var service = new DriveService(new BaseClientService.Initializer
        {
            HttpClientInitializer = cred
        });
        var files = await service.Files.List().ExecuteAsync();
        var fileNames = files.Files.Select(x => x.Name).ToList();
        return View(fileNames);
    }
          

אלה היסודות. אפשר לעיין בקוד של HomeController.cs מהפרויקט Google.Apis.Auth.AspNetCore3.IntegrationTests כדי לראות איך אפשר להשיג את הדברים הבאים:

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

חשבון שירות

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

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

using System;
using System.Security.Cryptography.X509Certificates;

using Google.Apis.Auth.OAuth2;
using Google.Apis.Plus.v1;
using Google.Apis.Plus.v1.Data;
using Google.Apis.Services;

namespace Google.Apis.Samples.PlusServiceAccount
{
    /// <summary>
    /// This sample demonstrates the simplest use case for a Service Account service.
    /// The certificate needs to be downloaded from the Google API Console
    /// <see cref="https://console.cloud.google.com/">
    ///   "Create another client ID..." -> "Service Account" -> Download the certificate,
    ///   rename it as "key.p12" and add it to the project. Don't forget to change the Build action
    ///   to "Content" and the Copy to Output Directory to "Copy if newer".
    /// </summary>
    public class Program
    {
        // A known public activity.
        private static String ACTIVITY_ID = "z12gtjhq3qn2xxl2o224exwiqruvtda0i";

        public static void Main(string[] args)
        {
            Console.WriteLine("Plus API - Service Account");
            Console.WriteLine("==========================");

            String serviceAccountEmail = "SERVICE_ACCOUNT_EMAIL_HERE";

            var certificate = new X509Certificate2(@"key.p12", "notasecret", X509KeyStorageFlags.Exportable);

            ServiceAccountCredential credential = new ServiceAccountCredential(
               new ServiceAccountCredential.Initializer(serviceAccountEmail)
               {
                   Scopes = new[] { PlusService.Scope.PlusMe }
               }.FromCertificate(certificate));

            // Create the service.
            var service = new PlusService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "Plus API Sample",
            });

            Activity activity = service.Activities.Get(ACTIVITY_ID).Execute();
            Console.WriteLine("  Activity: " + activity.Object.Content);
            Console.WriteLine("  Video: " + activity.Object.Attachments[0].Url);

            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
    }
}

הדוגמה הזו יוצרת ServiceAccountCredential. ההיקפים הנדרשים מוגדרים, ויש קריאה ל-FromCertificate, שמטעינה את המפתח הפרטי מ-X509Certificate2. כמו בכל דוגמאות הקוד האחרות, פרטי הכניסה מוגדרים כ-HttpClientInitializer.