Google 日历 API。向某人日历添加事件会在进行身份验证时抛出错误 "Error 401: invalid_client"
Google Calendar API. Adding an event to someone calendar throws error "Error 401: invalid_client" just when authenticating
我有一个 C# class 库,我试图通过使用 his/her 电子邮件地址和密码作为凭据将事件添加到某人的日历中。所以我调试它并在互联网浏览器中打开一个新页面并显示以下错误:
代码下方:
// It crashes when calling GoogleWebAuthorizationBroker.AuthorizeAsync
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "myGoogleAccount@gmail.com",
ClientSecret = "myGoogleAccountPasswordHere",
},
new[] { CalendarService.Scope.Calendar },
System.Environment.UserName,
CancellationToken.None).Result;
// Create the service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Calendar API Sample",
});
为什么会出现这个错误? ClientId 不是gmail 帐号?
还有为什么在互联网浏览器中打开一个新页面?我想在不在互联网浏览器中打开页面的情况下进行身份验证,因为这个 class 库是从 windows 服务调用的,所以我需要在后台完成身份验证。
答案:
为了将方法插入到用户的日历中,您需要用户授予您的应用程序代表他们执行操作的权限。这是使用 Google 云平台 (GCP) 项目和 OAuth2 身份验证完成的。
更多信息:
每个 运行 代表 Google 帐户用户执行操作的应用程序都必须明确定义其能力范围,这样它就不能开始执行用户无法执行的操作尚未授予它这样做的权限。
例如:如果您授予应用程序创建日历事件的权限,您不希望它能够执行其他操作,例如阅读您的电子邮件或下载您云端硬盘的内容。
为了指定您的应用程序有权执行的操作,需要使用 Google 进行注册。正如您已经在问题和评论中推断的那样,连接到 G Suite API 的应用程序所需的客户端 ID 和客户端密码不仅仅是 Google 帐户的用户名和密码,而是一个Google 提供的指定 ID-secret ID 对,用于识别您的应用程序。
OAuth2:
OAuth2 是一个特定的授权框架。该框架在 RFC 6749 中定义,并规定了用户可以授权应用程序访问其帐户的过程。授权的范围由授权的申请范围决定,未经用户明确重新授权不得更改。
在继续之前,有必要在这里定义一些重要的术语:
用户:
A user is the person; the individual that has an account and gives permission for an application to take actions on their behalf.
客户端或应用程序:
A Client or Application is a program which is designed to take actions over HTTP by connecting to a service's API. Applications can be mobile apps, web apps or desktop clients.
授权服务器:
An Authorisation server is a server which is separate from the servers that store user resources. It verfies the user's identity and provides a grant which can be used to get an access token to a resource server.
资源服务器:
This is the server where user data is stored. This could be anything from user information to files or emails.
授权流程已被详细记录,但为了这个场景,我们可以将其抽象为以下步骤:
- 应用程序希望代表用户在资源服务器上执行操作。
- 应用程序向用户发出授权请求。这通常显示为应用程序正在访问的帐户的登录页面。
- 用户登录到他们的帐户并看到一个 OAuth 同意屏幕 - 其中包含应用程序名称等信息,以及它请求授权的任务列表。这些通常是通用的,并且会说类似
See and download all your Google Drive files
或 View and edit events on all your calendars
的内容。这允许用户在确认之前知道他们正在授权什么。
- 已向应用程序授予授权。
- 应用程序将获得的授权授予及其分配的客户端凭据提供给授权服务器。
- 在验证用户的授权和客户端的凭据都正确时,授权服务器return会提供一个访问令牌,可用于访问请求和批准的资源。 注意:无论您使用哪种语言,这通常都由您的客户端库处理。
- 应用程序现在可以向资源服务器发出请求,提供从授权流程中获得的访问令牌。此时可以访问允许的资源。
Google 云平台项目:
一个 GCP 项目,Google 将其视为您的应用程序。您的应用程序注册需要能够获取您的应用程序需要的客户端 ID 和客户端密码,以便在授权流程中获取访问令牌。
在 GCP console you can set up all the required services that your application needs. Each API you wish to use has to be enabled for your application, as there are many Google services with APIs 中,默认情况下它们是禁用的。
创建 GCP 项目后,您可以使用 API 库(从左侧的 ≡ > APIs & Services > Library
菜单项)找到并启用 API。请注意,对于您的用例,您需要启用 Google 日历 API 而不是 CalDAV API.
在为您的应用程序获取凭据之前,您还需要设置同意屏幕。 OAuth 同意屏幕是您的用户将在 OAuth 流程的第一步中看到的内容:
设置 OAuth 同意屏幕时,您需要提供以下信息:
- 应用程序类型(public 或您的域内部)
- 应用名称
- 您的应用程序需要的范围(在下一节中解释)
同意屏幕设置完成后,您可以下载应用程序的客户端凭据。有了这些,您的应用程序就有了 运行 作为客户端的权限,但是每个访问其资源的用户仍然必须明确允许应用程序这样做。
范围:
在一个 API 中,可以有多个访问范围 - 对日历事件的只读访问权限与对用户拥有的所有日历的完全读写访问权限大不相同。这就是范围发挥作用的地方。
作用域被定义为同名的;也就是说,范围定义了应用程序对服务的访问范围。即使为项目启用了整个 API 并不意味着您需要使用 API 的所有功能。为此,需要定义范围。
范围是在发出初始用户授权请求之前在应用程序本身中定义的。在 C# 中,例如(取自 .NET Calendar API Quickstart):
// scopes are defined as an array of strings:
static string[] Scopes = { CalendarService.Scope.CalendarReadonly };
...
UserCredential credential;
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
存储的访问令牌基于调用中定义的范围。如果调用的方法需要与令牌授予访问权限的范围不同的范围,则调用将失败并出现 403: Unauthorized
错误。需要将所需的范围添加到应用程序,删除旧的访问令牌,并且用户需要授予新范围的权限。
服务帐户:
与普通用户一样,还有另一种特殊类型的 Google 帐户,称为服务帐户。来自文档:
A service account is a special kind of account used by an application or a virtual machine (VM) instance, not a person. Applications use service accounts to make authorized API calls.
通常,您希望为其执行任务或访问资源的每个用户都需要明确允许您的应用程序这样做。但是,对于 G Suite 域,您可以使用具有 domain-wide delegation 的服务帐户来代表用户完成任务,而不需要。
服务帐户使用一种特殊的服务帐户凭据,可以在 GCP 中创建并在您的应用程序中使用。而不是制作一个 UserCredential
对象,需要一个 ServiceAccountCredential
,它 doesn't require involvment by an end user.
当运行代表具有域范围委派的用户使用服务帐户时,需要在委派凭据中指定用户名,以便应用程序知道域中的哪个用户运行作为。如果未提供用户,服务帐户将 运行 代码作为自己;这在某些情况下很有用,但通常不会 return 错误,因此可能不清楚该操作是针对谁 运行.
注意:虽然任何人都可以创建服务帐户,但域范围的授权只能针对 G Suite 域完成,而不是 @gmail.com
地址.所有 Gmail 帐户用户 必须 根据 OAuth 流程的规定,代表他们向 运行 授予应用程序的明确许可。
参考文献:
- Using OAuth2 to Access Google APIs | Google Identity Platform
- RFC 6749 - The OAuth2 Authorization Framework
- OAuth - Wikipedia | #OAuth2
- Google Cloud Platform Console
- Google APIs Explorer | Google Developers
- .NET Quickstart | Calendar API | Google Developers
- Service Accounts | Cloud IAM Documentation | Google Cloud
- Perform G Suite Domain-Wide Delegation of Authority | Directory API
- OAuth 2.0 | API Client Library for .NET
相关问题:
我有一个 C# class 库,我试图通过使用 his/her 电子邮件地址和密码作为凭据将事件添加到某人的日历中。所以我调试它并在互联网浏览器中打开一个新页面并显示以下错误:
代码下方:
// It crashes when calling GoogleWebAuthorizationBroker.AuthorizeAsync
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "myGoogleAccount@gmail.com",
ClientSecret = "myGoogleAccountPasswordHere",
},
new[] { CalendarService.Scope.Calendar },
System.Environment.UserName,
CancellationToken.None).Result;
// Create the service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Calendar API Sample",
});
为什么会出现这个错误? ClientId 不是gmail 帐号? 还有为什么在互联网浏览器中打开一个新页面?我想在不在互联网浏览器中打开页面的情况下进行身份验证,因为这个 class 库是从 windows 服务调用的,所以我需要在后台完成身份验证。
答案:
为了将方法插入到用户的日历中,您需要用户授予您的应用程序代表他们执行操作的权限。这是使用 Google 云平台 (GCP) 项目和 OAuth2 身份验证完成的。
更多信息:
每个 运行 代表 Google 帐户用户执行操作的应用程序都必须明确定义其能力范围,这样它就不能开始执行用户无法执行的操作尚未授予它这样做的权限。
例如:如果您授予应用程序创建日历事件的权限,您不希望它能够执行其他操作,例如阅读您的电子邮件或下载您云端硬盘的内容。
为了指定您的应用程序有权执行的操作,需要使用 Google 进行注册。正如您已经在问题和评论中推断的那样,连接到 G Suite API 的应用程序所需的客户端 ID 和客户端密码不仅仅是 Google 帐户的用户名和密码,而是一个Google 提供的指定 ID-secret ID 对,用于识别您的应用程序。
OAuth2:
OAuth2 是一个特定的授权框架。该框架在 RFC 6749 中定义,并规定了用户可以授权应用程序访问其帐户的过程。授权的范围由授权的申请范围决定,未经用户明确重新授权不得更改。
在继续之前,有必要在这里定义一些重要的术语:
用户:
A user is the person; the individual that has an account and gives permission for an application to take actions on their behalf.
客户端或应用程序:
A Client or Application is a program which is designed to take actions over HTTP by connecting to a service's API. Applications can be mobile apps, web apps or desktop clients.
授权服务器:
An Authorisation server is a server which is separate from the servers that store user resources. It verfies the user's identity and provides a grant which can be used to get an access token to a resource server.
资源服务器:
This is the server where user data is stored. This could be anything from user information to files or emails.
授权流程已被详细记录,但为了这个场景,我们可以将其抽象为以下步骤:
- 应用程序希望代表用户在资源服务器上执行操作。
- 应用程序向用户发出授权请求。这通常显示为应用程序正在访问的帐户的登录页面。
- 用户登录到他们的帐户并看到一个 OAuth 同意屏幕 - 其中包含应用程序名称等信息,以及它请求授权的任务列表。这些通常是通用的,并且会说类似
See and download all your Google Drive files
或View and edit events on all your calendars
的内容。这允许用户在确认之前知道他们正在授权什么。 - 已向应用程序授予授权。
- 应用程序将获得的授权授予及其分配的客户端凭据提供给授权服务器。
- 在验证用户的授权和客户端的凭据都正确时,授权服务器return会提供一个访问令牌,可用于访问请求和批准的资源。 注意:无论您使用哪种语言,这通常都由您的客户端库处理。
- 应用程序现在可以向资源服务器发出请求,提供从授权流程中获得的访问令牌。此时可以访问允许的资源。
Google 云平台项目:
一个 GCP 项目,Google 将其视为您的应用程序。您的应用程序注册需要能够获取您的应用程序需要的客户端 ID 和客户端密码,以便在授权流程中获取访问令牌。 在 GCP console you can set up all the required services that your application needs. Each API you wish to use has to be enabled for your application, as there are many Google services with APIs 中,默认情况下它们是禁用的。
创建 GCP 项目后,您可以使用 API 库(从左侧的 ≡ > APIs & Services > Library
菜单项)找到并启用 API。请注意,对于您的用例,您需要启用 Google 日历 API 而不是 CalDAV API.
在为您的应用程序获取凭据之前,您还需要设置同意屏幕。 OAuth 同意屏幕是您的用户将在 OAuth 流程的第一步中看到的内容:
设置 OAuth 同意屏幕时,您需要提供以下信息:
- 应用程序类型(public 或您的域内部)
- 应用名称
- 您的应用程序需要的范围(在下一节中解释)
同意屏幕设置完成后,您可以下载应用程序的客户端凭据。有了这些,您的应用程序就有了 运行 作为客户端的权限,但是每个访问其资源的用户仍然必须明确允许应用程序这样做。
范围:
在一个 API 中,可以有多个访问范围 - 对日历事件的只读访问权限与对用户拥有的所有日历的完全读写访问权限大不相同。这就是范围发挥作用的地方。
作用域被定义为同名的;也就是说,范围定义了应用程序对服务的访问范围。即使为项目启用了整个 API 并不意味着您需要使用 API 的所有功能。为此,需要定义范围。
范围是在发出初始用户授权请求之前在应用程序本身中定义的。在 C# 中,例如(取自 .NET Calendar API Quickstart):
// scopes are defined as an array of strings:
static string[] Scopes = { CalendarService.Scope.CalendarReadonly };
...
UserCredential credential;
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
存储的访问令牌基于调用中定义的范围。如果调用的方法需要与令牌授予访问权限的范围不同的范围,则调用将失败并出现 403: Unauthorized
错误。需要将所需的范围添加到应用程序,删除旧的访问令牌,并且用户需要授予新范围的权限。
服务帐户:
与普通用户一样,还有另一种特殊类型的 Google 帐户,称为服务帐户。来自文档:
A service account is a special kind of account used by an application or a virtual machine (VM) instance, not a person. Applications use service accounts to make authorized API calls.
通常,您希望为其执行任务或访问资源的每个用户都需要明确允许您的应用程序这样做。但是,对于 G Suite 域,您可以使用具有 domain-wide delegation 的服务帐户来代表用户完成任务,而不需要。
服务帐户使用一种特殊的服务帐户凭据,可以在 GCP 中创建并在您的应用程序中使用。而不是制作一个 UserCredential
对象,需要一个 ServiceAccountCredential
,它 doesn't require involvment by an end user.
当运行代表具有域范围委派的用户使用服务帐户时,需要在委派凭据中指定用户名,以便应用程序知道域中的哪个用户运行作为。如果未提供用户,服务帐户将 运行 代码作为自己;这在某些情况下很有用,但通常不会 return 错误,因此可能不清楚该操作是针对谁 运行.
注意:虽然任何人都可以创建服务帐户,但域范围的授权只能针对 G Suite 域完成,而不是 @gmail.com
地址.所有 Gmail 帐户用户 必须 根据 OAuth 流程的规定,代表他们向 运行 授予应用程序的明确许可。
参考文献:
- Using OAuth2 to Access Google APIs | Google Identity Platform
- RFC 6749 - The OAuth2 Authorization Framework
- OAuth - Wikipedia | #OAuth2
- Google Cloud Platform Console
- Google APIs Explorer | Google Developers
- .NET Quickstart | Calendar API | Google Developers
- Service Accounts | Cloud IAM Documentation | Google Cloud
- Perform G Suite Domain-Wide Delegation of Authority | Directory API
- OAuth 2.0 | API Client Library for .NET