OAuth 2.0

В этом документе описывается протокол OAuth 2.0, случаи его использования, способы получения идентификаторов клиентов и его применение с клиентской библиотекой API Google для .NET.

Протокол OAuth 2.0

OAuth 2.0 – это протокол авторизации, используемый API Google. Рекомендуем ознакомиться с протоколом, перейдя по следующим ссылкам:

Получите идентификаторы клиентов и секретные коды

Идентификаторы клиентов и секретные коды можно получить в Google API Console. Существуют разные типы идентификаторов клиентов, поэтому убедитесь, что вы получили правильный тип для своего приложения:

Для каждого из приведенных фрагментов кода (кроме фрагмента для сервисного аккаунта) необходимо скачать секретный код клиента и сохранить его в проекте как client_secrets.json.

Учетные данные

Учетные данные пользователя

UserCredential – это потокобезопасный вспомогательный класс, который позволяет использовать токен доступа для работы с защищенными ресурсами. Обычно срок действия токена доступа истекает через час, после чего при попытке его использовать вы получите сообщение об ошибке.

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();
            ...
        }
    }
}
  
  • В этом образце кода новый экземпляр UserCredential создается путем вызова метода GoogleWebAuthorizationBroker.AuthorizeAsync. Этот статический метод получает следующие данные:

    • Секретный код клиента (или поток к секретному коду клиента).
    • Необходимые области действия.
    • Идентификатор пользователя.
    • Токен отмены для отмены операции.
    • Необязательное хранилище данных. Если хранилище данных не указано, по умолчанию используется 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)

API Google поддерживают OAuth 2.0 для веб-серверных приложений.

Для большинства сценариев OAuth 2.0 на основе Google в приложениях ASP.NET Core 3 рекомендуется использовать библиотеку Google.Apis.Auth.AspNetCore3. Он реализует обработчик аутентификации OpenIdConnect, предназначенный для Google. Она поддерживает пошаговую аутентификацию и определяет внедряемый IGoogleAuthProvider для предоставления учетных данных Google, которые можно использовать с Google API.

В этом разделе описывается, как настроить и использовать 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, следуйте этим инструкциям. Два основных момента:
    • Обратите внимание, что в качестве типа учетных данных должно быть указано Веб-приложение.
    • Для запуска этого приложения нужно добавить только один 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.
    using Google.Apis.Auth.AspNetCore3;
  • В метод Startup.ConfigureServices добавьте следующий код, заменив плейсхолдеры идентификатора клиента и секретного кода клиента значениями из файла client_secrets.json. Вы можете загрузить эти значения непосредственно из файла JSON или сохранить их любым другим безопасным способом. Пример загрузки этих значений непосредственно из файла JSON можно найти в методе ClientInfo.Load проекта Google.Apis.Auth.AspNetCore3.IntegrationTests.
    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();
    
        ...
    }
          

Использовать учетные данные пользователя для доступа к API Google от его имени.

Теперь вы можете добавить в контроллеры методы действий, которые требуют учетных данных пользователя для доступа к API Google от его имени. В приведенном ниже фрагменте кода показано, как получить список файлов в аккаунте Google Диска аутентифицированного пользователя. Обратите внимание на две вещи:

  • Пользователь должен не только пройти аутентификацию, но и предоставить вашему приложению доступ к области действия https://www.googleapis.com/auth/drive.readonly, которую вы указываете с помощью атрибута GoogleScopedAuthorize.
  • Мы используем стандартный механизм внедрения зависимостей ASP.NET Core 3, чтобы получить IGoogleAuthProvider, который мы используем для получения учетных данных пользователя.

Код:

  • Сначала добавьте в контроллер следующие директивы using:
    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 обнаружит, что срок действия токена доступа истек или близок к истечению, и автоматически обновит его.

Сервисный аккаунт

API Google также поддерживают сервисные аккаунты. В отличие от сценария, в котором клиентское приложение запрашивает доступ к данным конечного пользователя, сервисные аккаунты предоставляют доступ к собственным данным клиентского приложения.

Клиентское приложение подписывает запрос на получение токена доступа с помощью закрытого ключа, скачанного из Google API Console. После создания нового идентификатора клиента выберите тип приложения "Аккаунт сервиса", а затем скачайте закрытый ключ. Ознакомьтесь с примером сервисного аккаунта, использующего 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.