OAuth 2.0

本文档介绍了 OAuth 2.0、何时使用它、如何获取客户端 ID,以及如何将其与适用于 .NET 的 Google API 客户端库结合使用。

OAuth 2.0 协议

OAuth 2.0 是 Google API 使用的授权协议。您应通过阅读以下链接来熟悉该协议:

获取客户端 ID 和密钥

您可以在 Google API 控制台上获取客户端 ID 和密钥。客户端 ID 有不同的类型,因此请务必获取适用于您的应用的正确类型:

在以下代码段(服务帐号 1 除外)中,您必须下载客户端密钥并将其作为 client_secrets.json 存储在您的项目中。

凭据

用户凭据

UserCredential 是一个线程安全帮助程序类,用于使用访问令牌访问受保护的资源。 访问令牌通常会在 1 小时后过期,之后如果您尝试使用它,将会遇到错误。

UserCredentialAuthorizationCodeFlow 负责自动“刷新”令牌,即获取新的访问令牌。这是使用长期刷新令牌来完成的。如果您在授权代码流程中使用 access_type=offline 参数,则会收到该令牌和访问令牌。

在大多数应用中,建议将凭据的访问令牌和刷新令牌存储在永久性存储空间中。否则,您需要每小时向最终用户显示浏览器中的授权页面,因为访问令牌在您收到一小时后就会过期。

为了确保访问令牌和刷新令牌持久保留,您可以提供自己的 IDataStore 实现,也可以使用该库提供的以下实现之一:

  • .NET 的 FileDataStore 可确保凭据将永久保留在文件中。

ServiceAccountCredential

ServiceAccountCredentialUserCredential 类似,但用途不同。Google OAuth 2.0 支持服务器到服务器的互动,例如 Web 应用和 Google Cloud Storage 之间的服务器到服务器互动。发出请求的应用必须证明自己的身份才能访问 API,最终用户无需参与其中。ServiceAccountCredential 会存储一个私钥,该私钥用于对请求进行签名,以获取新的访问令牌。

UserCredentialServiceAccountCredential 都会实现 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();
            ...
        }
    }
}
  
  • 在此示例代码中,通过调用 GoogleWebAuthorizationBroker.AuthorizeAsync 方法创建一个新的 UserCredential 实例。此静态方法会获取以下信息:

    • 客户端密钥(或客户端密钥的流)。
    • 所需的范围。
    • 用户标识符。
    • 用于取消操作的取消令牌。
    • 可选的数据存储区。如果未指定数据存储区,则默认为具有默认 Google.Apis.Auth 文件夹的 FileDataStore。该文件夹将在 Environment.SpecialFolder.ApplicationData 中创建。
  • 此方法返回的 UserCredential 被设置为 BooksService 上的 HttpClientInitializer(使用初始化程序)。如上所述,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"));
          

请查看我们的图书示例

Web 应用 (ASP.NET Core 3)

Google API 支持 适用于 Web 服务器应用的 OAuth 2.0

Google.Apis.Auth.AspNetCore3 是推荐在 ASP.NET Core 3 应用中用于大多数基于 Google 的 OAuth 2.0 场景的库。它实现了 Google 专用的 OpenIdConnect 身份验证处理程序。它支持增量身份验证,并定义了可注入的 IGoogleAuthProvider,以提供可与 Google API 配合使用的 Google 凭据。

本部分介绍了如何配置和使用 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 应用
    • 如需运行此应用,您需要添加的唯一重定向 URI 是 https://localhost:5001/signin-oidc

将应用配置为使用 Google.Apis.Auth.AspNetCore3

Google.Apis.Auth.AspNetCore3 是在 Startup 类或您可能正在使用的类似替代方案中配置的。以下代码段提取自 Google.Apis.Auth.AspNetCore3.IntegrationTests 项目的 Startup.cs

  • 将以下 using 指令添加到您的 Startup.cs 文件中。
    using Google.Apis.Auth.AspNetCore3;
  • Startup.ConfigureServices 方法中添加以下代码,将客户端 ID 和客户端密钥占位符替换为 client_secrets.json 文件中包含的值。您可以直接从 JSON 文件加载这些值,也可以用任何其他安全方式存储它们。请查看 Google.Apis.Auth.AspNetCore3.IntegrationTests 项目中的 ClientInfo.Load 方法,通过示例了解如何直接从 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 API

现在,您可以将操作方法添加到需要用户凭据才能代表其访问 Google API 的控制器中。以下代码段展示了如何列出经过身份验证的用户的 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);
    }
          

这些只是基本步骤。您可以查看 Google.Apis.Auth.AspNetCore3.IntegrationTests 项目中的 HomeController.cs,了解如何实现以下目标:

  • 仅限用户身份验证,无特定范围
  • 退出功能
  • 通过代码进行增量授权。请注意,上面的代码段显示了通过属性进行增量授权。
  • 查看当前授予的范围
  • 检查访问令牌和刷新令牌
  • 强制刷新访问令牌。请注意,您无需自行执行此操作,因为 Google.Apis.Auth.AspNetCore3 会检测访问令牌是否已过期或即将过期,并且会自动刷新该访问令牌。

服务账号

Google API 还支持服务帐号。与客户端应用请求访问最终用户数据的情形不同,服务帐号提供对客户端应用自身数据的访问权限。

您的客户端应用使用从 Google API 控制台下载的私钥对访问令牌请求进行签名。创建新的客户端 ID 后,您应选择“服务帐号”应用类型,然后即可下载私钥。请查看 使用 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