You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

urllib.request带Bearer认证重定向请求失败的问题排查

Python urllib请求GitHub工件API返回403认证失败

场景重现

以下curl命令可以正常从GitHub工作流下载工件:

curl -L -H "Authorization: Bearer ghp_XXXX" -o arti.zip \
     https://api.github.com/repos/OWNER/REPO/actions/artifacts/ID/zip

但使用相同URL和令牌的Python urllib代码却返回403错误:

import os, urllib.request
req = urllib.request.Request('https://api.github.com/repos/OWNER/REPO/actions/artifacts/ID/zip')
req.add_header('Authorization', 'Bearer ghp_XXXX')

with urllib.request.urlopen(req) as input:
    with open('arti.zip', 'wb') as output:
        output.write(input.read())

错误信息:

urllib.error.HTTPError: HTTP Error 403: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.

问题根源

GitHub工件API的初始请求会重定向到第三方存储域名(比如Azure Blob存储的域名),而urllib的默认规则是不会把敏感头(比如Authorization)传递给不同主机的重定向请求——哪怕官方文档提到add_header的头会跟随重定向,这个规则只适用于同主机的重定向。

curl的-L参数之所以能成功,是因为它对GitHub相关的重定向目标做了特殊处理,允许传递认证头;但urllib遵循更严格的安全规范,直接跳过了跨主机的敏感头传递,导致重定向后的请求没有携带令牌,触发403。


基于urllib的修复方案

要让认证头跟随跨主机重定向,需要自定义重定向处理器,手动把Authorization头添加到重定向请求中:

方案1:全局修改opener(影响所有urllib请求)

import urllib.request

class AuthRedirectHandler(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        # 调用父类生成默认的重定向请求
        new_req = super().redirect_request(req, fp, code, msg, headers, newurl)
        # 手动复制原请求的Authorization头到新请求
        if 'Authorization' in req.headers:
            new_req.add_header('Authorization', req.headers['Authorization'])
        return new_req

# 安装自定义处理器
opener = urllib.request.build_opener(AuthRedirectHandler())
urllib.request.install_opener(opener)

# 正常发起请求
req = urllib.request.Request('https://api.github.com/repos/OWNER/REPO/actions/artifacts/ID/zip')
req.add_header('Authorization', 'Bearer ghp_XXXX')

with urllib.request.urlopen(req) as input:
    with open('arti.zip', 'wb') as output:
        output.write(input.read())

方案2:仅针对当前请求使用自定义opener(更安全)

如果不想影响全局的urllib行为,可以直接用自定义opener发起请求:

import urllib.request

class AuthRedirectHandler(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        new_req = super().redirect_request(req, fp, code, msg, headers, newurl)
        if 'Authorization' in req.headers:
            new_req.add_header('Authorization', req.headers['Authorization'])
        return new_req

# 创建opener但不全局安装
opener = urllib.request.build_opener(AuthRedirectHandler())

req = urllib.request.Request('https://api.github.com/repos/OWNER/REPO/actions/artifacts/ID/zip')
req.add_header('Authorization', 'Bearer ghp_XXXX')

# 使用自定义opener发起请求
with opener.open(req) as input:
    with open('arti.zip', 'wb') as output:
        output.write(input.read())

注意事项

  • 这种方式绕过了urllib的跨主机敏感头限制,只建议在明确信任重定向目标的场景下使用(比如GitHub官方的工件存储服务)。
  • 虽然requests库能更简洁地处理这个问题(默认跟随重定向并携带认证头),但如果必须使用标准库urllib,以上方案就是最直接的解决办法。

内容的提问来源于stack exchange,提问作者Thierry Lelegard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 23:50:14