谷歌日历 API。向某人日历添加事件在身份验证时会引发错误“错误 401:invalid_client”

2024-03-05

我有一个 C# 类库,我试图通过使用他/她的电子邮件地址和密码作为凭据来将事件添加到某人的日历中。所以我对其进行调试,一旦启动,互联网浏览器中就会打开一个新页面,并显示以下错误:

代码如下:

// It crashes when calling GoogleWebAuthorizationBroker.AuthorizeAsync
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                new ClientSecrets
                {
                    ClientId = "[email protected] /cdn-cgi/l/email-protection",
                    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帐户吗? 另外为什么互联网浏览器中会打开一个新页面?我想在不打开互联网浏览器页面的情况下进行身份验证,因为这个类库是从 Windows 服务调用的,所以我需要在后台完成身份验证。


Answer:

为了将方法插入到用户的日历中,您需要用户授予您的应用程序代表他们执行操作的权限。这是使用 Google Cloud Platform (GCP) 项目和 OAuth2 身份验证完成的。

更多信息:

每个代表 Google 帐户用户运行并采取操作的应用程序都必须明确定义其能力范围,以便它无法开始执行用户未授予其执行权限的操作。

例如:如果您授予应用程序创建日历活动的权限,您不希望它能够执行其他操作,例如阅读电子邮件或下载云端硬盘的内容。

为了指定您的应用程序有权执行的操作,它需要向 Google 注册。正如您在问题和评论中已经推断出的那样,连接到 G Suite API 的应用程序所需的客户端 ID 和客户端秘密不仅仅是 Google 帐户的用户名和密码,而是指定的 ID-秘密 ID 对,由 Google 提供,用于识别您的应用程序。

OAuth2:

OAuth2是一个特定的授权框架。该框架定义于RFC 6749 https://www.rfc-editor.org/rfc/rfc6749并列出了用户授权应用程序访问其帐户的流程。授权的限制由授权应用的范围决定,未经用户明确重新授权,不得更改。

在继续之前,有必要在这里定义一些重要术语:

User:

用户就是人;拥有帐户并允许应用程序代表其采取行动的个人。

客户或应用程序:

客户端或应用程序是一个程序,旨在通过连接到服务的 API 通过 HTTP 执行操作。应用程序可以是移动应用程序、Web 应用程序或桌面客户端。

授权服务器:

授权服务器是与存储用户资源的服务器分开的服务器。它验证用户的身份并提供可用于获取资源服务器访问令牌的授权。

资源服务器:

这是存储用户数据的服务器。这可以是从用户信息到文件或电子邮件的任何内容。

授权流程已经有详细记录,但对于这种情况,我们可以将其抽象为以下步骤:

  • 应用程序希望代表用户在资源服务器上执行操作。
  • 应用程序向用户发出授权请求。这通常显示为应用程序正在访问的帐户的登录页面。
  • 用户登录其帐户后会看到一个 OAuth 同意屏幕 - 其中包含应用程序名称及其请求授权的任务列表等信息。这些通常是通用的,并且会说类似的话See and download all your Google Drive files or View and edit events on all your calendars。这可以让用户知道what他们在确认之前就已经授权了。
  • 向该申请授予授权。
  • 应用程序将获得的授权许可及其分配的客户端凭证提供给授权服务器。
  • 在验证用户的授权和客户端的凭据是否正确后,授权服务器返回一个访问令牌,该令牌可用于访问请求和批准的资源。注意:无论您使用哪种语言,这通常都由您的客户端库处理.
  • 应用程序现在可以向资源服务器发出请求,提供从授权流程中获得的访问令牌。此时就可以访问允许的资源。

谷歌云平台项目:

一个 GCP 项目,Google 将其视为您的应用程序。您的应用程序的注册需要能够获取您的应用程序需要的客户端 ID 和客户端密钥,以便在授权流程中获取访问令牌。 在里面GCP 控制台 https://console.cloud.google.com/您可以设置应用程序所需的所有必需服务。您希望使用的每个 API 都必须为您的应用程序启用,如下所示有很多带有 API 的 Google 服务 https://developers.google.com/apis-explorer并且默认情况下它们是禁用的。

创建 GCP 项目后,您可以使用 API 库(来自≡ > APIs & Services > Library左侧的菜单项)来查找并启用 API。请注意,对于您的用例,您将需要启用 Google Calendar API 而不是 CalDAV API。

在获取应用程序的凭据之前,您还需要设置同意屏幕。 OAuth 同意屏幕是您的用户在 OAuth 流程的第一步中将看到的内容:

设置 OAuth 同意屏幕时,您需要提供以下信息:

  • 应用程序类型(您的域的公共或内部)
  • 应用名称
  • 您的应用程序需要的范围(在下一节中解释)

设置同意屏幕后,您可以下载应用程序的客户端凭据。有了这些,您的应用程序就有权作为客户端运行,但每个访问其资源的用户仍然必须给予明确的许可才能允许应用程序这样做。

Scopes:

在单个 API 中,可以有多个访问范围 - 对日历事件进行只读访问与对用户拥有的所有日历进行完全读写访问有很大不同。这就是作用域发挥作用的地方。

范围的定义与其同名;也就是说,范围定义了应用程序对服务的访问范围。即使项目启用了整个 API,并不意味着您需要使用该 API 的所有功能。因此,需要定义范围。

在发出用户授权的初始请求之前,范围是在应用程序本身中定义的。以 C# 为例(取自.NET 日历 API 快速入门 https://developers.google.com/calendar/quickstart/dotnet):

// 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 帐户,称为服务帐户。从文档中:

服务帐户是应用程序或虚拟机 (VM) 实例(而不是个人)使用的一种特殊帐户。应用程序使用服务帐户进行授权的 API 调用。

通常,您希望为其执行任务或访问资源的每个用户都需要明确授予您的应用程序这样做的权限。但是,对于 G Suite 域,您可以使用服务帐户域范围的委派 https://developers.google.com/admin-sdk/directory/v1/guides/delegation在没有要求的情况下代表用户完成任务。

服务帐户使用一种特殊类型的服务帐户凭据,可以在 GCP 中创建并在您的应用程序中使用。而不是制作一个UserCredential对象,一个ServiceAccountCredential需要哪个不需要最终用户的参与 https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#serviceaccountcredential.

当使用域范围委派代表用户运行服务帐户时,需要在委派凭据中指定用户名,以便应用程序知道要以域中的哪个用户身份运行。如果未提供用户,服务帐户将自行运行代码;这在某些情况下很有用,但通常不会返回错误,因此可能不清楚该操作是为谁运行的。

Note:虽然任何人都可以创建服务帐户,但域范围内的授权只能针对 G Suite 域完成,而不能@gmail.com地址。所有 Gmail 帐户用户must按照 OAuth 流程的规定,明确授予应用程序代表其运行的权限。

参考:

  • 使用 OAuth2 访问 Google API |谷歌身份平台 https://developers.google.com/identity/protocols/oauth2
  • RFC 6749 - OAuth2 授权框架 https://www.rfc-editor.org/rfc/rfc6749
  • OAuth - 维基百科| #OAuth2 https://en.wikipedia.org/wiki/OAuth#OAuth_2.0
  • 谷歌云平台控制台 https://console.cloud.google.com/
  • Google API 资源管理器 |谷歌开发者 https://developers.google.com/apis-explorer
  • .NET 快速入门 |日历 API |谷歌开发者 https://developers.google.com/calendar/quickstart/dotnet
  • 服务帐户 |云 IAM 文档 |谷歌云 https://cloud.google.com/iam/docs/service-accounts
  • 执行 G Suite 域范围内的授权 |目录API https://developers.google.com/admin-sdk/directory/v1/guides/delegation
  • OAuth 2.0 | .NET 的 API 客户端库 https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth

相关问题:

  • 谷歌 API 服务帐户。即使使用域范围委派访问也只能看到服务帐户驱动器 https://stackoverflow.com/questions/60892662/google-api-service-account-can-only-see-service-accounts-drive-even-with-domain/60927643
  • 使用 Google Calendar API 和服务帐户创建活动 https://stackoverflow.com/questions/61473708
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

谷歌日历 API。向某人日历添加事件在身份验证时会引发错误“错误 401:invalid_client” 的相关文章

  • 模板类包装任意类型/非类型模板类

    假设我有一个模板类base和一个班级wrapper其中包含一个实例化成员base 我想定义班级wrapper这样它依赖于模板参数包 该参数包只是 传递 给实例化成员base 例如 考虑下面的代码 它工作得很好 include
  • 高级 Win32 图像文件 I/O?

    我想在 Windows C 应用程序中将图像文件读入内存 什么是一个相当简单的解决方案 也许类似于 IOS 提供的UIImage 我希望支持合理数量的文件格式 我需要为图像处理的位图提供一些低级访问权限 我在互联网上阅读了很多内容 看起来
  • 如何从当前 .NET 表单/应用程序发送密钥 F12

    我非常确定以下按钮激活的表单代码应该在我的 C 应用程序中引发 Control F12 SendKeys F12 但它似乎并没有继续进入 Windows shell 并激活另一个正在侦听它的程序 我的键盘可以用 看起来发送键在某处被拦截 并
  • 处理 LINQ sum 表达式中的 null

    我正在使用 LINQ 查询来查找列的总和 并且在少数情况下该值有可能为空 我现在使用的查询是 int score dbContext domainmaps Where p gt p SchoolId schoolid Sum v gt v
  • 有没有比这更快的方法来查找目录和所有子目录中的所有文件?

    我正在编写一个程序 需要在目录及其所有子目录中搜索具有特定扩展名的文件 这将在本地驱动器和网络驱动器上使用 因此性能是一个问题 这是我现在使用的递归方法 private void GetFileList string fileSearchP
  • Linq Where 本地计数器关闭在 VS watch 中的结果不同

    我尝试删除前 3 个元素array与 LinQWhere扩展功能 这是一个例子 var array new 1 2 3 4 5 6 7 8 9 var count 3 var deletedTest1 0 var test1 array W
  • 身份未映射异常

    System Security Principal IdentityNotMappedException 无法转换部分或全部身份引用 该错误仅在应用程序注册后出现一次 当 SecurityIdentifier 无法映射时 例如 返回 Ide
  • __FUNCTION__ 宏的 C# 版本

    有人对 C FUNCTION 宏的 C 版本有好的解决方案吗 编译器似乎不喜欢它 尝试使用这个代替 System Reflection MethodBase GetCurrentMethod Name C 没有 LINE or FUNCTI
  • 阅读 Stack Overflow RSS 源

    我正在尝试获取未回答问题的列表the feed https stackoverflow com feeds 但我在阅读时遇到困难 const string RECENT QUESTIONS https stackoverflow com f
  • C++ 在 Vector 中使用不可分配的对象

    我想将对象列表存储在std vector 但对象包含引用且无法分配给 但是 我可以复制构造该对象 我能想到的唯一选择是使用指针来包装对象并在需要分配指针时重新设置指针 但这样做的语法会显着降低可读性 特别是在使用迭代器时 我更喜欢另一种选择
  • C中有const吗?

    这个问题可能很幼稚 但是 有没有constC 中的关键字 从哪个版本开始 之间有任何语义和 或句法差异吗const在 C 和 C 中 C 和 C 之间在语法上没有差异const关键字 除了一个相当晦涩的关键字 在 C 中 自 C99 起 您
  • 防止复制构造和返回值引用的分配

    如果我有一个函数返回对类实例的引用 但我无法控制其源 比如说list
  • 捕获当前正在播放的声音

    是否可以捕获计算机上当前播放的声音 如果能够将其保存为 mp3 就好了 但我认为这样做会存在一些法律问题 所以 wav 也可以 我环顾四周 有人建议使用虚拟音频线之类的东西 在 C 中捕获声音输出 https stackoverflow c
  • CMake - 将预构建库链接到 C# 项目

    我正在使用 CMake 构建 C 库 该库依赖于已构建的库 dll 我似乎无法让图书馆链接到我的图书馆 我尝试过使用target link libraries mylib external lib 我也尝试过暴力破解 reference e
  • 在 C# 中赋值后如何保留有关对象的信息?

    我一直在问我的想法可能是解决方案 https stackoverflow com questions 35254467 is it possible in c sharp to get the attributes attached to
  • 标准 C 中的 sizeof 与 sizeof()? [复制]

    这个问题在这里已经有答案了 我看到一些直接使用 sizeof 的代码 想知道它是否是标准 C 令我惊讶的是 它运行得很好 这是一个例子 include
  • 如何在 C++ 中使用 PI 常数

    我想在一些 C 程序中使用 PI 常数和三角函数 我得到三角函数include
  • 如何使用 Clang 查找内存泄漏

    我在我的机器 ubuntu 中安装了 Clang 以便发现我的 C 代码中的内存泄漏 我编写了一个示例代码来检查它的工作情况 如下所示 File hello c for leak detection include
  • ASP.NET Core:会话 ID 始终变化

    今天启动了一个全新的 ASP NET Core 网站 按照说明添加会话 我们在索引页上打印出会话 ID 它始终是唯一的 我认为这可能是 cookie 合规性 所以我在 Chrome 的高级设置和调试器中删除了所有 cookie 但横幅不会再
  • 当我读取 500MB FileStream 时出现 OutOfMemoryException

    我使用 Filestream 读取大文件 gt 500 MB 但出现 OutOfMemoryException 任何有关它的解决方案 我的代码是 using var fs3 new FileStream filePath2 FileMode

随机推荐