Python请求:POST请求丢失授权头

12
我正在尝试使用Python requests库进行API POST请求。我正在传递Authorization头,但是当我尝试调试时,我可以看到该头被删除了。我不知道发生了什么。
这是我的代码:
access_token = get_access_token()
bearer_token = base64.b64encode(bytes("'Bearer {}'".format(access_token)), 'utf-8')
headers = {'Content-Type': 'application/json', 'Authorization': bearer_token}
data = '{"FirstName" : "Jane", "LastName" : "Smith"}'
response = requests.post('https://myserver.com/endpoint', headers=headers, data=data)

如您所见,我在请求参数中手动设置了Authorization标头,但实际请求的标头缺失: {'Connection': 'keep-alive', 'Content-Type': 'application/json', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'User-Agent': 'python-requests/2.4.3 CPython/2.7.9 Linux/4.1.19-v7+'}

另外需要提供的信息是,如果我将POST请求更改为GET请求,则Authorization标头可以正常传递!

为什么这个库会在POST请求中删除标头,如何让它正常工作?

使用 v2.4.3 的 requests 库和 Python 2.7.9。


检查您的主目录是否有一个~/.netrc文件,并将其重命名。 这可能不是OP的问题,但具有类似的症状。 - MarkHu
8个回答

22

简述

你请求的URL重定向POST请求到另一个主机,因此requests库会删除Authorization头以防泄露你的凭据。要解决这个问题,可以在requests的Session类中覆盖负责处理的方法。

详细信息

在requests 2.4.3中,当请求被重定向到另一个主机时,requests仅会删除Authorization头。这是相关代码:

if 'Authorization' in headers:
    # If we get redirected to a new host, we should strip out any
    # authentication headers.
    original_parsed = urlparse(response.request.url)
    redirect_parsed = urlparse(url)

    if (original_parsed.hostname != redirect_parsed.hostname):
        del headers['Authorization']
在更新的requests版本中,Authorization标头在额外情况下将被删除(例如如果重定向是从安全协议到非安全协议)。所以你的情况可能发生的是,你的POST请求被重定向到了不同的主机。使用requests库为重定向的主机提供身份验证的唯一方法是通过一个.netrc文件。遗憾的是,这只允许使用HTTP基本身份验证,这对你没有太大帮助。在这种情况下,最好的解决方案可能是子类化requests.Session并覆盖此行为,像这样:
from requests import Session

class NoRebuildAuthSession(Session):
    def rebuild_auth(self, prepared_request, response):
        """
        No code here means requests will always preserve the Authorization
        header when redirected.
        Be careful not to leak your credentials to untrusted hosts!
        """

session = NoRebuildAuthSession()
response = session.post('https://myserver.com/endpoint', headers=headers, data=data)

编辑

我在 GitHub 上向 requests 库提交了一个 pull-request,以添加一个警告,当发生这种情况时会出现。它已经等待第二次批准合并了(已经三个月了)。


1
谢谢,这就是问题所在! - user4184113
1
专业提示:检查您的~/.netrc文件! --文档位于https://docs.python-requests.org/en/master/user/quickstart/#custom-headers和https://docs.python-requests.org/en/master/user/authentication/#netrc-authentication中简要提到此事。 - MarkHu
"netrc文件覆盖了通过headers=设置的原始HTTP身份验证头。" - 看起来现在您可以使用基本认证以外的身份验证方法!这对您有用吗? - kmaork

2

以下是请求文档中的说明:

使用headers=设置的授权标头将被覆盖,如果在.netrc中指定了凭据,则该标头将被覆盖auth=参数。 如果您被重定向到不同的主机,授权标头将被删除。

您的请求是否被重定向?

如果是这种情况,请在post请求中使用以下选项禁用重定向:

allow_redirects=False


allow_redirects=False 只会阻止请求跟随服务器请求的重定向。这并不能帮助完成请求,只会在中途停止它。 - kmaork
~/.netrc 文件很容易被忘记。¯\(ツ) - MarkHu

0

循环请求对我很有用

    response = do_request(url, access_tok, "GET", payload={}, headers={}, allow_redirect=False)

    if response.status_code in range(300, 310):
        new_response = do_request(response.headers['Location'], access_tok, "GET", payload={}, headers={},)
        # print(new_response.status_code)
        pprint(new_response.json())

0
从文档中可以看到:请求将尝试从用户的netrc文件中获取URL主机名的身份验证凭据。 netrc文件会覆盖使用headers=设置的原始HTTP身份验证头。 如果找到主机名的凭据,请求将使用HTTP基本身份验证发送。
如果您被重定向,可以尝试使用allow_redirects=false。

0
我的问题是在.netrc中有一个条目,它覆盖了授权标头。其他答案提到过.netrc,但没有解决这个问题。解决方案是手动创建一个Session并将trust_env设置为False。
import requests
session = requests.Session()
session.trust_env = False
headers={'Authorization': f'Bearer {TOKEN}'}
session.post(url, headers=headers)

有一个GitHub问题可以防止这种覆盖。


0

我看到的第一个(也许是实际的)问题是您如何创建bearer_token,因为您不仅编码了令牌,还编码了认证类型'Bearer'

据我所理解,您只需要对令牌进行编码,并在请求头中提供空白认证类型和编码后的令牌:

bearer_token = str(base64.b64encode(access_token.encode()), "utf8")
headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(bearer_token)}

如果这也是一个重定向问题,您可以简单地找到正确的位置并向该URL发出请求,或者考虑在您的POST请求体中发送访问令牌(如果服务器接受此操作)。

-1

使用Python中的'request'库在POST请求中发送Authorization头,只需使用以下代码:

requests.post('https://api.github.com/user', auth=('user', 'pass'))

这是一个基本认证。


-1

你可以尝试在头部使用自定义授权。

定义一个自定义认证类:

class MyAuth(requests.auth.AuthBase):
def __init__(self, bearer_token):
    self.username = None
    self.bearer_token = bearer_token

def __call__(self, r):
    r.headers['Authorization'] = self.bearer_token
    return r

然后使用这个来发送请求:

headers = {'Content-Type': 'application/json'}

data = '{"FirstName" : "Jane", "LastName" : "Smith"}'

response = requests.post('https://myserver.com/endpoint', headers=headers, auth=MyAuth(bearer_token), data=data)

如果这个方法有效,请接受答案。如果您仍然有问题,请告诉我们。 希望能帮到您。

不需要继承自requests.auth.AuthBase。如果你查看它的源代码,你会发现它所做的就是在你忘记重写__call__时引发NotImplemented - Lord Elrond
这不会改变问题中描述的行为。在重定向时重新构建身份验证时,requests不使用auth参数。 - kmaork

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接