【问题标题】:Safely distribute OAuth 2.0 client_secret in desktop applications in Python使用 Python 在桌面应用程序中安全分发 OAuth 2.0 client_secret
【发布时间】:2021-02-28 01:12:27
【问题描述】:

我正在寻找一些关于如何在 Python 中设计和创建我自己的桌面应用程序(或 installed app)的最佳实践示例代码,需要向 Google 提供 OAuth 2.0 授权流程,并找到了 Google 提供的这个存储库:https://github.com/googlesamples/oauth-apps-for-windows(用 C# 编码,但无论如何设计应该是相同的)。

在深入研究代码时,我惊讶地发现client_secret 直接清晰地嵌入到源代码中(看这里:https://github.com/googlesamples/oauth-apps-for-windows/blob/e79f1575b5858c5f617d29f2435a93996e4248c5/OAuthConsoleApp/OAuthConsoleApp/Program.cs#L47)。

我在Google Developers documentation about "Installed applications" 上找到了这个:

当您通过 Google API 控制台创建客户端 ID 时,指定这是一个已安装的应用程序,然后选择 Android、Chrome、iOS 或“其他”作为应用程序类型。该过程会生成一个客户端 ID,在某些情况下,还会生成一个客户端密码,您可以将其嵌入到应用程序的源代码中。 (在这种情况下,客户端机密显然不被视为机密。)

另外,我不知道为什么 Android 或 iOS 应用程序不会在控制台生成的 OAuth 客户端 ID 中包含这个 client_secret,而其他本机应用程序(桌面)应该包含。

而且我还在许多网站上发现,客户端机密应该保密……正如其名称所暗示的那样。

我已经阅读了本地应用程序的不同 RFC(我相信最可靠的来源)并发现这很有用:

https://datatracker.ietf.org/doc/html/draft-ietf-oauth-native-apps-12#appendix-A

  1. 不假定本机应用程序客户端可以保密。如果机密被分发到同一本机应用程序的多次安装,则不应将其视为机密。请参阅第 8.5 节。

但我想确保我理解正确。

那么,在从 Google API 控制台为“其他”应用程序类型生成 OAuth 客户端 ID 后,是否可以将客户端密码直接嵌入到我的应用程序中?这样做真的没有安全问题吗?这个 SO 帖子:What the attacker could do if he obtains application's client_secret? 谈论安全问题,所以我有点迷茫。

使用google-auth-oauthlib避免从头实现OAuth协议,我可以安全地分发以下代码吗(****的值显然不会被混淆):

from google_auth_oauthlib import flow

# generated from Google API Console ("other" application)
client_config = {
  "installed": {
    "client_id": "****.apps.googleusercontent.com",
    "client_secret": "****", # is it safe?
    "project_id": "****",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "redirect_uris": [
      "urn:ietf:wg:oauth:2.0:oob"
    ]
  }
}

scopes = ['https://www.googleapis.com/auth/books'] # example
appflow = flow.InstalledAppFlow.from_client_config(client_config, scopes=scopes)
appflow.run_console()
credentials = appflow.credentials

# some code requesting Google APIs for the required scopes

如果恶意用户发现了client_secret,他可以用这个做什么?

【问题讨论】:

  • 我也有同样的问题。以下两个答案似乎表明 client_secret 对于本机应用程序不需要保密:stackoverflow.com/q/20558863/3040129stackoverflow.com/q/44312000/3040129
  • @illabout 感谢您提供有用的链接。我仍然不明白 Android/iOS(不包括 client_secret)和桌面应用程序(需要嵌入 client_secret)之间的区别。就像您在其中一个帖子中所说的那样,它看起来像是一个“奇怪”。另外,我主要关心的是安全性,我不知道恶意用户可以用client_secret 做什么,即使这种情况下它不被视为真正的“秘密”。我找不到任何可靠的来源(预计是 RFC)谈论这个。
  • @norbjd 您最终为此采用了什么解决方案?我一直在绕圈子试图回答这个问题,但我觉得我永远学不到足够的知识来解决这个问题。
  • @AaronCiuffo 在我们的例子中,我们决定在源代码中添加 client_secret,就像在 Google 示例中一样。但是这个应用程序的用户是我们公司的用户,所以使用 client_secret 做坏事的风险被降低了,因为只有少数已知用户使用我们的应用程序。
  • @norbjd 我猜对用户级谷歌应用程序的需求不足,因此没有更好的解决方案。这有点令人沮丧,但除非我想让我的应用基于网络,否则我将不得不忍受它。

标签: python-3.x security oauth-2.0 google-oauth


【解决方案1】:

当公共客户端(例如,本机和单页应用程序)请求访问令牌时,会产生一些额外的安全问题,这些问题无法单独通过授权代码流来缓解。这是因为:

原生应用
无法安全地存储客户端密码。反编译应用程序将显示客户端密钥,该密钥绑定到应用程序并且对所有用户和设备都是相同的。 可能会使用自定义 URL 方案来捕获重定向(例如 MyApp://),这可能允许恶意应用程序从您的授权服务器接收授权代码。

单页应用
无法安全地存储客户端密钥,因为它们的整个源都可供浏览器使用。

为了缓解这种情况,OAuth 2.0 提供了一个授权代码流版本,该版本使用代码交换证明密钥 (PKCE)(在 OAuth 2.0 RFC 7636 中定义)。

PKCE-enhanced Authorization Code Flow 引入了一个由调用应用程序创建的可以被授权服务器验证的秘密;这个秘密被称为代码验证器。此外,调用应用程序创建称为代码质询的代码验证器的转换值,并通过 HTTPS 发送此值以检索授权代码。这样一来,恶意攻击者只能截获授权码,而没有验证码他们无法将其换成令牌。

延伸阅读:https://auth0.com/docs/flows/concepts/auth-code-pkce

【讨论】:

  • 您的解释非常清楚,但与其他人(例如 Dropbox)相反,Google 的实现是,一旦收到授权码,将其交换为令牌需要客户端发送其私钥。因此,该密钥必须嵌入分布式应用程序中(混淆它是浪费时间,因为它可以被逆向工程)。我找到了谷歌的句子“在这种情况下,客户端秘密显然不被视为秘密”。来自一家声称教授安全性的公司可笑 - developers.google.com/identity/protocols/oauth2
  • 我也不明白那部分。当您在源代码中嵌入客户端 ID 和客户端密码时,如何阻止不良行为者将这些凭据复制到他们自己的应用中,并让他们的用户尽可能多地进行 API 调用,直到达到限制?
  • @Akashdeep S 你能详细说明一下吗?我正在编写一个开源 python 项目。这个我还是有点懵。 client_secret 是否实际上是一个秘密,如果它在野外可能会被滥用。什么可以阻止某人在他们自己的项目中使用我公开的 client_secret?
【解决方案2】:

我遇到了同样的问题,Google 要求 client_secret 用于桌面应用程序。虽然攻击者显然无法通过client_secret 获取机密信息,但他不能通过使用client_secret 生成大量虚假请求并导致谷歌阻止它来发起拒绝服务攻击吗? 这将需要开发人员创建一个新的客户端密码并重新分发给所有用户??

【讨论】:

  • 您找到解决方案或最佳实践了吗?我正在尝试创建一个我可以分发的开源应用程序,但如果最终用户必须弄清楚如何创建自己的密钥然后自己构建应用程序,这将毫无用处。
【解决方案3】:

没有任何特定的用户数据附加到客户端密码,因为使用 client_id 和 client_secret 的任何人都需要通过 OAuth 屏幕对其 Google 帐户进行身份验证。

但实际问题似乎是您正在使用生成的凭据访问 Google API,而这些 API 调用不是免费的。最后,您的应用程序需要为通过您的应用程序进行身份验证的用户进行的 API 调用付费。

【讨论】:

  • 感谢您的回答。您确定计费部分吗?您的答案的第一部分和第二部分有可靠的来源吗?非常感谢。
  • 他们的文档中没有写,但谷歌地图等一些 API 需要设置账单。如果您没有 Google 地图,您将无法向其发出任何 API 请求,即使您的用户已通过身份验证。您将获得 $X 积分以开始使用,因此这些 API 调用显然不是免费的。
猜你喜欢
  • 2011-08-04
  • 2010-10-30
  • 2020-06-18
  • 2021-08-12
  • 2012-11-25
  • 1970-01-01
  • 2011-02-13
  • 2011-04-14
  • 1970-01-01
相关资源
最近更新 更多