由于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。
我的第一个想法是要恢复的变化prepare_request_body
到,默认情况下,使用self.client_id
在设定WebApplicationClient
构造函数。
然后,应充分更改内联文档以将&client_id=xx
到prepare_request_body
输出中。
最后,为了替换原来的修复,我建议从 args 中删除client_id
,并向prepare_request_body
添加一个新参数,如include_client=True/False
以添加client_id
和client_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=False
在prepare_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/False
( True
(默认):它添加了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_id
和client_secret
auth
并且不在正文中包含它们(RFC 首选解决方案)
self._client.prepare_request_body(include_client_id=False, ..)
auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
C) 在正文中包含client_id
和client_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 并知道以下问题的答案:
username
+ password
甚至应该是 request.body 中的参数吗?username
+ password
出现在 HTTP Basic Auth 标头中,它们是否应该在请求正文中重复?username
+ password
正文参数和带有客户端详细信息的 HTTPBasicAuth 标头?感谢您遇到这个问题。
username
和password
必须始终存在于请求正文中。 它们必须仅用于密码授予又名遗产。 这些不得用于其他授权(隐式、代码、客户端凭据)。client_secret
)。 用户凭据绝不能在 HTTP 基本身份验证中。谢谢。 只是为了澄清两件事,请随时和我说话,就像我五岁一样。 我想确保我得到这个和正确的测试:
除非它们被明确指定,否则它们不应该出现,对吗?
请原谅我对这些小细节的冗长和执着。 我只想确保 j 获得正确的行为,并且可以编写测试以确保我们不会再次出现回归。
@jvanasco ,我可以讲述 OAuth2 RFC,但是我不确定requests-oauthlib
和flask-oauthlib
组合在一起的。
正确的。
这是我的理解,但最好结合该领域的实际情况进行整理; 即请求-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 password
和client secret
之间存在混淆,这实际上是完全相同的两个词。
如果我们遵循https://github.com/requests/requests-oauthlib/pull/206背后的基本原理,PR 的内容不应该像添加HTTPAuth(username, password)
而应该是HTTPAuth(client_id, client_secret
(客户的密码)。
戳@Lukasa 、 @chaosct 、 @ibuchanan参与了 requests-oauthlib 的讨论。
伟大的! 非常感谢。 我认为这就是正在发生的事情,但想确认一下。
我想我知道我现在想要如何构建请求的内容。 我在主要请求项目上有一些提交,所以我知道维护者喜欢在 PR 和功能中看到什么。
- 用户名和密码仅用于需要它们的特定类型的授权。 如果使用,它们可能只出现在请求正文中。
是的,第一部分:只有某种类型的赠款需要它们。 但是在关于在请求正文中发送它们的第二部分,规范说:
在请求正文中包含客户端凭据
不推荐使用这两个参数
并且应该仅限于无法直接使用的客户
HTTP 基本身份验证方案...
但是对于服务器,它显示为:
授权服务器可以支持包括
请求正文中的客户端凭据...
合规的客户端不会在请求正文中发送凭据。
但是,对于一些部分实现的服务器,它们只会在请求正文中接受它们。
如果我没记错的话,我的 PR 通过添加 auth 标头解决了这种混淆,
所以客户端发送两者。
我相信@JonathanHuot关于第二点是正确的。
嗨@ibuchanan ,您所指的引号使用术语client credentials
。 我们必须非常小心,不要将客户端与资源所有者(实际用户)混在一起。
这些角色在rfc6749#1.1 中有清楚的解释。
此client credentials
指的是client_id
和client_secret
,资源所有者指的是username
和password
。 这些是不可互换的。
因此,通过阅读具有这些角色的 RFC 意味着兼容客户端应该在 HTTP Basic 中发送客户端凭据( client_id
, client_secret
)并且必须发送用户凭据( username
, password
)在请求正文中(永远不要在这里阅读替代方案); 见rfc6749#4.3.2 。
如果 client_id 在请求中,某些服务器会拒绝请求 (400)
身体。 我认为默认值应该是规范推荐的。
好的。 我认为oauthlib
的当前 PR 满足上述问题 - include_client_id
标志明确允许发送或不发送client_id
。
就requests_oauthlib
,这就是我的想法:
username
和password
只会出现在正文中(不是 HTTP Basic)。 如果需要对非兼容服务器整合这种行为,实现者可以提交auth
或headers
参数fetch_token()
。
在正确的位置提供client_id
有点烦人,但我认为我已经掌握了逻辑和用例。 这肯定需要一些审查。
我对@JonathanHuot和@ibuchanan 的一个问题:
oauthlib
的 OAuth2 Client
和requests_oauthlib
的OAuth2Session
不保留client_secret
并且必须重复调用它。 在 OAuth1 中情况并非如此,我认为这是 #370 背后的实际问题。 RFC 没有提到需要这样做,我找不到任何历史记录。
对我来说,通过存储client_secret
扩展客户端是有意义的,并开始弃用 OAuth2Session 依赖于在fetch_token
和request
上传递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
这个有点激烈,因为查看代码和测试,该库似乎只是试图让各种不应该的东西工作。
修复做了几件事:
检查username
和password
仅适用于LegacyApplicationClient
实例——因为这应该是唯一需要的类型(对吗?)。 检查下有一个部分将用户名/密码合并到 kwargs 字典中。 它目前已缩进,因为测试表明其他客户端可能希望传递此信息。
重新编写了处理 client_id/auth 标头的逻辑,以确保默认情况下正确类型的 auth 发生在正确的位置。 如果用户想以另一种方式强制使用凭据,仍然有可能。
问题:有没有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
|
或另有说明:
@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被修复。
最有用的评论
不会看到您如何想用不同的值覆盖
client_id
,因此如果它们不同,则会投票支持引发异常。另外,如果
client_id
是作为 kwarg 提供的,我们是否应该记录DeprecationWarning
?