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

Go net/http Client重定向时降级敏感请求头的处理方案

来源:17golang原创

时间:2026-09-15 21:53:24 110浏览 收藏

服务端返回 301、302 或 307 时,Go 的 http.Client 不只是换一个 URL:它还会决定哪些请求头进入下一跳。默认策略会保护跨不可信域名的敏感头,但同域、子域以及同一主机换 scheme 的边界并不等同于严格的 origin。要让重定向明确降级,做法是给 Client.CheckRedirect 配置一层 origin 判断,在边界变化时删除凭据头,再决定是否继续。

要点速览
  • 默认规则会对不匹配域名忽略 AuthorizationWWW-AuthenticateCookie,但子域与同主机换 scheme 仍可能保留敏感头。
  • CheckRedirect 收到的是即将发送的请求和历史请求,可用最后一跳比较 scheme、host、端口。
  • 自定义的 X-API-Key 不属于默认保护清单,跨 origin 时应由业务策略显式删除。

官方地址:https://pkg.go.dev/net/http。本文只讨论 Go net/http Client 的重定向请求头边界,不扩展到 HTTP 框架选型。

默认重定向规则为什么不等于严格同源

Client 跟随重定向时,会把初始请求的普通头带到后续请求;对敏感头,默认规则会在目标域名既不是原域名、也不是原域名的子域时忽略它们。例如 api.example.comcdn.example.com 仍属于子域匹配,而到 other.example.net 则不会继续带凭据。

这个规则解决的是“不要把凭据发给完全无关域名”,却不是严格的 origin 策略:同一 host 从 HTTPS 跳到 HTTP,或者从一个端口跳到另一个端口,仍可能落在默认转发范围内。若业务把 scheme、端口也视为信任边界,就需要覆盖 CheckRedirect

Go net/http Client 重定向时按域名匹配转发敏感请求头的静态边界说明图
图1:Go net/http Client 默认重定向规则的边界说明图,不是运行截图或执行证据。

用 CheckRedirect 比较前后请求的 origin

回调里的 req 是即将发送的下一跳,via 按从旧到新的顺序保存已发请求。比较 via 最后一项与 req 的 scheme 和 host,可以得到比“同域或子域”更清晰的同源判定。

package main

import (
    "net/http"
    "strings"
)

// sameOrigin 只把 scheme、主机和端口都相同的地址视为同一信任边界。
func sameOrigin(a, b *http.Request) bool {
    return strings.EqualFold(a.URL.Scheme, b.URL.Scheme) &&
        strings.EqualFold(a.URL.Host, b.URL.Host)
}

// redirectPolicy 读取上一跳请求,再处理即将发送的下一跳请求。
func redirectPolicy(req *http.Request, via []*http.Request) error {
    if len(via) == 0 {
        return nil // 第一跳没有历史请求,不需要比较。
    }
    previous := via[len(via)-1]
    if !sameOrigin(previous, req) {
        // 跨 origin 时删除凭据;X-API-Key 是业务自定义敏感头。
        req.Header.Del("Authorization")
        req.Header.Del("Cookie")
        req.Header.Del("X-API-Key")
    }
    return nil // 返回 nil 才允许 Client 继续跟随重定向。
}

var client = &http.Client{CheckRedirect: redirectPolicy}

这里的策略比默认行为更保守:子域跳转也会清理头,端口变化同样会清理。普通的 User-Agent、追踪 ID 等非凭据头可以继续沿用,但不要把“所有 Header 都安全”作为假设。

跳转情况默认 Client上面策略
同域同 scheme 同端口通常保留敏感头保留
子域或端口变化可能保留清理敏感头
完全不同域名忽略敏感头清理敏感头

自定义 API 密钥要在回调里显式降级

默认保护清单并不知道业务自定义的 X-API-Key、租户签名或内部追踪凭据。只配置 Authorization 的删除是不够的,应该把所有真正代表身份或授权的头集中列入策略。Cookie 还要注意 Jar:有非空 Cookie Jar 时,客户端会在每次重定向前后更新和重新装配 Cookie,不能只依赖初始请求里的 Header。

Go CheckRedirect 比较前后 origin 后清理 Authorization Cookie 和 X-API-Key 的静态关系说明图
图2:CheckRedirect 的 origin 判断、敏感头清理与后续请求关系说明图,不是运行截图。

如果业务根本不接受自动跳转,可以直接停止链路,让上层读取响应的 Location 再做白名单判断:

client := &http.Client{
    CheckRedirect: func(req *http.Request, via []*http.Request) error {
        // 中文注释:到达重定向回调时不再自动发送下一跳,保留最后响应供调用方判断。
        return http.ErrUseLastResponse
    },
}

resp, err := client.Get("https://api.example.com/resource")
if err != nil {
    // 中文注释:网络错误或客户端策略错误都要交给调用方处理。
    return err
}
defer resp.Body.Close() // 中文注释:ErrUseLastResponse 返回的响应体由调用方关闭。
// 中文注释:此处可读取 resp.Location,并按业务白名单决定是否发起新请求。

307/308、响应体和重定向次数的检查清单

301、302、303 通常会把后续方法改成 GET;307、308 会保留原方法和请求体,但前提是 Request.GetBody 可用。上传类请求尤其要确认请求体能否重放,不要因为清理了 Header 就误以为重定向链已经安全。

  • 只允许预期的 scheme、host 和端口,或在 CheckRedirect 中遇到异常直接返回错误。
  • AuthorizationCookie、API 密钥、租户签名等业务凭据列成明确清单。
  • Do 成功返回后读取并关闭 resp.Body;使用 ErrUseLastResponse 时也一样。
  • 复用同一个 http.Client,并为重定向链保留最大次数控制,避免无限跳转消耗请求预算。

相关问题

为什么跳到子域时 Authorization 还在?

这是 Go 默认规则的一部分:同域或子域匹配时允许转发敏感头。如果子域不是同一信任边界,应在 CheckRedirect 中主动删除。

删除 Cookie Header 就能清掉 Cookie 吗?

不一定。配置 Cookie Jar 后,Jar 可能根据响应重新装配匹配域名的 Cookie;需要同时限制允许的目标 origin,必要时不用 Jar 或停止自动重定向。

CheckRedirect 返回普通 error 后响应体谁关闭?

客户端文档规定策略错误返回的上一响应体已关闭;如果返回 ErrUseLastResponse,最后响应体保持打开,必须由调用方关闭。

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