登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go HTTP 重定向为什么丢失 Authorization

来源:17golang原创

时间:2026-10-25 23:24:38 413浏览 收藏

调用 Go 的 HTTP 客户端时,第一跳明明带着 Authorization,重定向后的请求却变成了未认证请求。这个现象通常不是 Header 被随机清空,而是 http.Client 在构造下一跳时按目标地址判断敏感头能否继续转发。

要点速览
  • Authorization 属于敏感请求头,跳到不可信域名时 Go 默认会移除它。
  • 同一主机或初始域名的子域,默认策略与完全不同的主机不是一回事;排查时要同时记录原始 URL、下一跳 URL 和目标主机。
  • 生产代码优先在 CheckRedirect 里限制信任边界;只对明确允许的下一跳重新设置凭据,不要无条件跨域转发。

先记录一组可复现的重定向基线

排查时不要只看最终的 401。最小基线应包含第一跳状态码、Location、下一跳 URL,以及下一跳是否仍有 Authorization。例如第一跳是 302,目标从 api.example.com 变成 login.other.example,第二个请求没有认证头,这个结果已经足以把范围收窄到重定向策略。

下面的诊断回调只记录地址和是否存在请求头,不打印令牌本身。它适合临时定位问题,也能避免把敏感凭据写进日志。

client := &http.Client{
    // 在真正发出下一跳前观察请求,不输出 Authorization 的值。
    CheckRedirect: func(next *http.Request, via []*http.Request) error {
        hasAuth := next.Header.Get("Authorization") != ""
        log.Printf("redirect target=%s has_authorization=%t", next.URL.String(), hasAuth)
        return nil
    },
}
Go HTTP 重定向中初始请求、302 Location、下一跳请求和 Authorization 观察指标的静态关系图
图1:重定向基线结构图,关注第一跳和下一跳的地址与 Authorization 是否存在,不是运行截图。

Authorization 为什么会在下一跳消失

Go 官方 net/http.Client 文档把 Authorization、WWW-Authenticate 和 Cookie 归为敏感头。跟随重定向时,如果目标域名既不是初始域名,也不是它的子域,客户端会把这些头视为不应转发到不可信目标的凭据。

因此,下面几种情况要分开判断:

重定向目标默认判断排查重点
同一主机通常保留初始请求头确认是否被自定义 CheckRedirect 改写
初始域名的子域Go 的默认策略可能继续转发敏感头是否真的属于同一信任边界
完全不同的主机移除 Authorization 等敏感头不要为了“让请求成功”直接跨域补回
HTTPS 跳 HTTP应视为降级风险通常直接拒绝或改为手动处理

这里的“丢失”是安全边界的结果,不是服务器把请求头改没了。CheckRedirect 收到的 next 已经是即将发送的下一跳请求,正好可以用来做最后一道信任判断。

用 CheckRedirect 把凭据限制在同一信任边界

更稳妥的做法是先定义允许的重定向范围:协议和主机都必须与初始请求一致,满足条件时再显式保留认证头;否则返回错误,让调用方处理 3xx,而不是把令牌送往未知主机。

func sameTrustBoundary(origin, target *url.URL) bool {
    // 示例策略只允许同协议、同主机,生产环境可扩展为明确的 allowlist。
    return origin.Scheme == target.Scheme && origin.Host == target.Host
}

func clientWithRedirectPolicy() *http.Client {
    return &http.Client{
        CheckRedirect: func(next *http.Request, via []*http.Request) error {
            if len(via) == 0 {
                return nil
            }

            origin := via[0]
            // 不把凭据带到不同协议或不同主机,避免跨边界泄露。
            if !sameTrustBoundary(origin.URL, next.URL) {
                return fmt.Errorf("拒绝跨信任边界重定向到 %s", next.URL.Host)
            }

            // 只在已确认同一边界时恢复初始请求的 Authorization。
            next.Header.Set("Authorization", origin.Header.Get("Authorization"))
            return nil
        },
    }
}

这段代码的关键不是“强行补 Header”,而是先拒绝未知目标,再在允许范围内设置它。若业务确实需要跳到固定的认证域,应把目标主机写进明确的 allowlist,并为每个允许目标定义独立凭据,不要把任意 Location 都当成可信地址。

Go CheckRedirect 以协议和主机为边界决定 Authorization 保留或拒绝的静态结构图
图2:信任边界结构图,展示 CheckRedirect、同边界允许分支和跨边界拒绝分支的静态关系,不是运行截图。

别把状态码、方法和凭据问题混在一起

301、302、303 跟随后通常会把后续请求改成 GET,并且不再携带原请求体;307 和 308 才会保留原方法和请求体,但需要 Request.GetBody 能够重放。也就是说,即使你成功保留了 Authorization,请求仍可能因为方法或 Body 变化而得到不同结果。

如果只想拿到 3xx 由业务自己解析,可以在 CheckRedirect 返回 http.ErrUseLastResponse。此时调用方要按文档处理返回的响应体;如果返回普通错误,Client.Do 可能同时给出上一跳响应和一个包装后的 url.Error,不要把它误判成网络连接失败。

常见问题

为什么设置了 CheckRedirect 还是没有 Authorization?

先确认回调确实被调用,再看 next.URL.Host 与初始主机是否一致。回调中若返回错误,下一跳不会发送;若目标跨域,默认复制逻辑也会先剥离敏感头,需要在已允许的信任边界内显式设置。

把 Authorization 无条件复制到下一跳可以吗?

不建议。重定向地址通常来自服务器响应,直接复制会把令牌交给未知主机。应先做协议、主机或 allowlist 判断,再决定是否发送。

同一个域名下为什么仍然认证失败?

继续检查是否发生了 HTTPS 到 HTTP 的降级、CheckRedirect 是否清空了 Header、状态码是否导致 POST 改成 GET,以及服务端是否要求新的 Cookie 或其他认证材料。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>