Oauthlib: 客户端 Web 应用程序不再发送 client_id

创建于 2018-09-08  ·  26评论  ·  资料来源: oauthlib/oauthlib

由于https://github.com/oauthlib/oauthlib/issues/495已被合并,我在与 requests/requests-oauthlib 一起使用时发现了 master 中的回归。 它仅与授权授予/网络应用程序相关。

requests-oauthlib 的基本用法是:

sess = OAuth2Session(client_id)
token = sess.fetch_token(token_url, client_secret=client_secret, authorization_response=request.url)

但是,由于更改,会话的client_id将被忽略。 我认为https://github.com/oauthlib/oauthlib/pull/505修复了一个用例但破坏了另一个用例。 我们应该找到一个双赢的解决方案。

requests-oauthlib 代码调用在https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L196 -L198 和 oauthlib 问题在这里
https://github.com/oauthlib/oauthlib/blame/master/oauthlib/oauth2/rfc6749/clients/web_application.py#L128。

Bug Discussion OAuth2-Client

最有用的评论

不会看到您如何想用不同的值覆盖client_id ,因此如果它们不同,则会投票支持引发异常。

另外,如果client_id是作为 kwarg 提供的,我们是否应该记录DeprecationWarning

所有26条评论

我的第一个想法是要恢复的变化prepare_request_body到,默认情况下,使用self.client_id在设定WebApplicationClient构造函数。
然后,应充分更改内联文档以将&client_id=xxprepare_request_body输出中。

最后,为了替换原来的修复,我建议从 args 中删除client_id ,并向prepare_request_body添加一个新参数,如include_client=True/False以添加client_idclient_secret在正文中,或者不包括它们。

想法?

@Diaoul @skion @thedrow

关于什么:

我建议从 args 中删除 client_id,并向 prepare_request_body 添加一个新参数,如 include_client=True/False 以在正文中添加 client_id 和 client_secret,或者不包含它们。

谢谢

我实际上在我的一个测试中触发了同样的问题,但在此处针对 requests/oauthlib 提交了它: https :

我相信这个问题实际上是 requests_oauthlib 的错。 他们的文档 - 实际上是加载页面时他们整个文档中的第一个示例 - 支持在OAuth2Session构造函数中指定client_id 。 第 200 行的逻辑从 kwargs 中提取client_id ,但没有回退从已经构建的WebApplicationClient实例中提取它。

@jvanasco ,当前问题 #585 可以单独在 requests-oauthlib 中修复,或者通过恢复 oauthlib 的 PR #505。 但是,没有一个解决方案可以解决@skion在他的评论中提到的行为https://github.com/oauthlib/oauthlib/pull/505#issuecomment -351221107

根据规范,必须为未经身份验证的客户端发送 client_id 参数,但最好不要在机密客户端的令牌请求正文中发送,因为在这种情况下,对客户端进行身份验证的首选机制是通过 HTTP 基本身份验证。 然而,WebApplication 类总是包含它(这会破坏一些服务器)并且没有提供删除它的机制。

Oauthlib 必须为 requests-oauthlib 提供一种优雅而简单的方式来解决问题。 如果我们能在这次讨论中找到解决方案,那就太好了; 因为这是一个巨大的障碍。

将允许client_id=Falseprepare_request_body()帮助,以表示CLIENT_ID不发送? 尽管即使是这样,这也会导致第 126 行附近的丑陋:

client_id = None if client_id=False else self.client_id

啊,我明白了。

是否有关于何时应该发送 client_id 和不发送的现有单元测试? 如果没有,是否有人拥有可用于生成列表的列表? 我很乐意尝试修复这个问题和 requests-oauthlib,因为它现在阻止了我的一些工作。

@乔纳森霍特

我建议从 args 中删除 client_id,并向 prepare_request_body 添加一个新参数,如 include_client=True/False 以在正文中添加 client_id 和 client_secret,或者不包含它们。

读到这里,我其实很喜欢你的建议。 我们可能会以一种不间断的方式避免这样做,因为该函数已经接受了**kwargs

一注:

在正文中同时添加 client_id 和 client_secret

由于这是关于公共客户 IIUC,我认为不涉及client_secret ; 只是将client_id添加到正文中? 在这种情况下,我还会考虑将新参数重命名为include_client_id=True/False

在这种情况下,我还会考虑将新参数重命名为 include_client_id=True/False。

的确 ! client_secret不涉及,因为它不存在于WebApplicationClient

@jvanasco ,如果您想做公关,我认为更改应该是:
1) 恢复 https://github.com/oauthlib/oauthlib/pull/505
2) 更改prepare_request_body()签名以删除client_id并添加include_client_id=True/FalseTrue (默认):它添加了self.client_id

然后, requests-oauthlib 将在 https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L196-L211 中选择:
A) 在正文中单独包含client_id

self._client.prepare_request_body(..)

B) 在auth包含client_idclient_secret auth并且不在正文中包含它们(RFC 首选解决方案)

self._client.prepare_request_body(include_client_id=False, ..)
auth = requests.auth.HTTPBasicAuth(client_id, client_secret)

C) 在正文中包含client_idclient_secret (RFC 替代解决方案)

self._client.prepare_request_body(client_secret=client_secret, ..)

今天我将为这两个项目生成 PR。

我已经为 OAuthlib 完成了 PR 和测试。 但我有个问题...

client_id还应该被允许作为 kwarg 吗? 这部分是为了向后兼容,但也适用于边缘情况。 因为这个方法有点坏,我认为可能值得让它按预期工作(如在prepare_request_body中允许它覆盖 self.client_id)或在不正确的使用中引发异常(如在引发如果提供了client_id但与self.client_id不匹配,则会出现错误)。

不会看到您如何想用不同的值覆盖client_id ,因此如果它们不同,则会投票支持引发异常。

另外,如果client_id是作为 kwarg 提供的,我们是否应该记录DeprecationWarning

PR #593 已提交。 它在提交client_id时引发 DeprecationWarning ,如果与self.client_id不同则引发 ValueError 。 还有一项新测试可确保符合@JonathanHuot详述的三个场景。

在编写一些测试时遇到了 requests-oauthlib PR 候选人的第一个问题

总是username=username, password=password调用prepare_request_body username=username, password=password 。 这似乎是错误的。 我希望这里有人更熟悉 RFC 并知道以下问题的答案:

  1. username + password甚至应该是 request.body 中的参数吗?
  2. 如果username + password出现在 HTTP Basic Auth 标头中,它们是否应该在请求正文中重复?
  3. 是否可以同时拥有username + password正文参数和带有客户端详细信息的 HTTPBasicAuth 标头?

感谢您遇到这个问题。

  1. usernamepassword必须始终存在于请求正文中。 它们必须用于密码授予又名遗产。 这些不得用于其他授权(隐式、代码、客户端凭据)。
  2. HTTP 基本身份验证对于客户端凭据是可选的(推荐用于机密客户端,即带有client_secret )。 用户凭据绝不能在 HTTP 基本身份验证中。
  3. 是的

谢谢。 只是为了澄清两件事,请随时和我说话,就像我五岁一样。 我想确保我得到这个和正确的测试:

  1. 用户名和密码仅用于需要它们的特定类型的授权。 如果使用,它们可能只出现在请求正文中。

除非它们被明确指定,否则它们不应该出现,对吗?

  1. 如果 Http 基本身份验证仅用于客户端凭据,则现有的 requests-oauthlib 不应具有从用户名和密码组合生成基本身份验证的块。

请原谅我对这些小细节的冗长和执着。 我只想确保 j 获得正确的行为,并且可以编写测试以确保我们不会再次出现回归。

@jvanasco ,我可以讲述 OAuth2 RFC,但是我不确定requests-oauthlibflask-oauthlib组合在一起的。

  1. 是的

正确的。

  1. 是的,AFAIU 它不应该有这个块https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L207 -L211。

这是我的理解,但最好结合该领域的实际情况进行整理; 即请求-oauthlib 和不同的公共提供商体验。 多个请求-oauthlib 讨论https://github.com/requests/requests-oauthlib/issues/218 , https://github.com/requests/requests-oauthlib/issues/211 , https://github.com/requests /requests-oauthlib/issues/264 ,已经发生了。

我相信他们在client passwordclient secret之间存在混淆,这实际上是完全相同的两个词。
如果我们遵循https://github.com/requests/requests-oauthlib/pull/206背后的基本原理,PR 的内容不应该像添加HTTPAuth(username, password)而应该是HTTPAuth(client_id, client_secret (客户的密码)。

@Lukasa@chaosct@ibuchanan参与了 requests-oauthlib 的讨论。

伟大的! 非常感谢。 我认为这就是正在发生的事情,但想确认一下。

我想我知道我现在想要如何构建请求的内容。 我在主要请求项目上有一些提交,所以我知道维护者喜欢在 PR 和功能中看到什么。

  1. 用户名和密码仅用于需要它们的特定类型的授权。 如果使用,它们可能只出现在请求正文中。

是的,第一部分:只有某种类型的赠款需要它们。 但是在关于在请求正文中发送它们的第二部分,规范说:

在请求正文中包含客户端凭据
不推荐使用这两个参数
并且应该仅限于无法直接使用的客户
HTTP 基本身份验证方案...

但是对于服务器,它显示为:

授权服务器可以支持包括
请求正文中的客户端凭据...

合规的客户端不会在请求正文中发送凭据。
但是,对于一些部分实现的服务器,它们只会在请求正文中接受它们。
如果我没记错的话,我的 PR 通过添加 auth 标头解决了这种混淆,
所以客户端发送两者。

我相信@JonathanHuot关于第二点是正确的。

@ibuchanan ,您所指的引号使用术语client credentials 。 我们必须非常小心,不要将客户端与资源所有者(实际用户)混在一起。

这些角色在rfc6749#1.1 中有清楚的解释。
client credentials指的是client_idclient_secret ,资源所有者指的是usernamepassword 。 这些是不可互换的。

因此,通过阅读具有这些角色的 RFC 意味着兼容客户端应该在 HTTP Basic 中发送客户端凭据client_idclient_secret )并且必须发送用户凭据usernamepassword )在请求正文中(永远不要在这里阅读替代方案); 见rfc6749#4.3.2

如果 client_id 在请求中,某些服务器会拒绝请求 (400)
身体。 我认为默认值应该是规范推荐的。

好的。 我认为oauthlib的当前 PR 满足上述问题 - include_client_id标志明确允许发送或不发送client_id

requests_oauthlib ,这就是我的想法:

  1. usernamepassword只会出现在正文中(不是 HTTP Basic)。 如果需要对非兼容服务器整合这种行为,实现者可以提交authheaders参数fetch_token()

  2. 在正确的位置提供client_id有点烦人,但我认为我已经掌握了逻辑和用例。 这肯定需要一些审查。

我对@JonathanHuot@ibuchanan 的一个问题:

oauthlib的 OAuth2 Clientrequests_oauthlibOAuth2Session不保留client_secret并且必须重复调用它。 在 OAuth1 中情况并非如此,我认为这是 #370 背后的实际问题。 RFC 没有提到需要这样做,我找不到任何历史记录。

对我来说,通过存储client_secret扩展客户端是有意义的,并开始弃用 OAuth2Session 依赖于在fetch_tokenrequest上传递client_secret request赞成在客户端本身中使用现在的那个。 (这也将解决 https://github.com/requests/requests-oauthlib/issues/264 中报告的一些其他不一致问题)

我稍微更改了 oauthlib (#593) 的 PR,以标准化用户名/密码和 client_id/client_secret 的测试变量。 我认为这应该可以防止将来混淆两者之间的错误。

requests-oauthlib 的建议更改: https ://github.com/requests/requests-oauthlib/compare/master...jvanasco :fix-oauthlib_client_id

这个有点激烈,因为查看代码和测试,该库似乎只是试图让各种不应该的东西工作。

修复做了几件事:

  1. 检查usernamepassword仅适用于LegacyApplicationClient实例——因为这应该是唯一需要的类型(对吗?)。 检查下有一个部分将用户名/密码合并到 kwargs 字典中。 它目前已缩进,因为测试表明其他客户端可能希望传递此信息。

  2. 重新编写了处理 client_id/auth 标头的逻辑,以确保默认情况下正确类型的 auth 发生在正确的位置。 如果用户想以另一种方式强制使用凭据,仍然有可能。

  3. 问题:有没有client_secret不通过的情况? 我想不出任何,但有很多 oAuth 流程。

所以在 requests-oauthlib 版本中:

| include_client_id | auth | 行为 |
| ------------------- | --------------- | -------- |
| 无(默认) | 无(默认) | 使用client_id创建一个 Auth 对象。 不要在正文中发送 client_id。 这是默认行为,因为 RFC 推荐它。 |
| 无(默认) | 礼物 | 使用 Auth 对象。 使用include_client_id=False调用 oauthlib 的prepare_request_body include_client_id=False |
| 错误 | 礼物 | 使用 Auth 对象。 使用include_client_id=False调用 oauthlib 的prepare_request_body include_client_id=False |
| 真| 礼物 | 使用 Auth 对象。 使用include_client_id=True调用 oauthlib 的prepare_request_body include_client_id=True |
| 真| 无(默认) | 使用client_id创建一个 Auth 对象。 使用include_client_id=True调用 oauthlib 的prepare_request_body include_client_id=True |
| 错误 | 无(默认) | 使用client_id创建一个 Auth 对象。 使用include_client_id=False调用 oauthlib 的prepare_request_body include_client_id=False |

或另有说明:

  • 创建一个认证对象

    • 不要在正文中发送客户端 ID:

    • (include_client_id=None, auth=None)

    • (include_client_id=False, auth=None)

    • 在正文中发送客户端 ID:

    • (include_client_id=True, auth=None)

  • 使用显式身份验证对象

    • 不要在正文中发送 client_id:

    • (include_client_id=None, auth=authObject)

    • (include_client_id=False, auth=authObject)

    • 在正文中发送 client_id:

    • (include_client_id=True, auth=authObject)

@jvanasco :在 oauthlib 和 requests-oauthlib 方面看起来非常好。

关于你的 3. 问题:

您可以拥有没有 client_secret 的公共客户端(或为空,从 RFC 的角度来看是接近的); 所以python API 必须支持“无秘密”。

实际用例通常用于您更喜欢使用授权代码的本机应用程序,但您不能保证周围的安全性保持机密安全,因此,您要么接受client_id而没有client_secret (或空的client_secret与 RFC 相同); 或者您还有另一个 PKCE RFC 可用(请参阅 https://oauth.net/2/pkce/)。 但后者尚未在oauthlib端实现。

谢谢。 我将添加一些测试用例以确保我们可以在没有client_secret情况下提交client_id client_secret 。 很抱歉问了这么多问题,这个规范可能有这么多正确的实现(甚至还有更多需要工作的损坏实现)。

@JonathanHuot现有实现不支持为client_secret发送空字符串。 它在此逻辑中被删除https://github.com/oauthlib/oauthlib/blob/master/oauthlib/oauth2/rfc6749/parameters.py#L90 -L125 -- 特别是第 122 行

支持它可以在该例程之后立即添加类似的内容:

if ('client_secret' in kwargs) and ('client_secret' not in params):
    if kwargs['client_secret'] == '':
        params.append((unicode_type('client_secret'), kwargs['client_secret']))

当秘密是空字符串时,这将发送client_secret的空字符串,但如果值为None ,则不会发送client_secret None

我认为这是值得支持的,因为如果 RFC 支持这两种变体中的任何一种......很可能有许多只支持一种变体的损坏实现。

此原始问题已在 oauthlib 中修复。 然而,这种行为仍然存在,直到https://github.com/requests/requests-oauthlib/pull/331被修复。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

polamayster picture polamayster  ·  19评论

ryarnyah picture ryarnyah  ·  3评论

JonathanHuot picture JonathanHuot  ·  15评论

ggiill picture ggiill  ·  7评论

JonathanHuot picture JonathanHuot  ·  33评论