Traefik RedirectScheme 中间件完整指南:将 HTTP 请求安全重定向到 HTTPS 与自定义端口
【免费下载链接】traefikThe Cloud Native Application Proxy项目地址: https://gitcode.com/GitHub_Trending/tr/traefik
技术导读:本文系统讲解 Traefik 官方 HTTP 中间件
RedirectScheme——当请求使用的 scheme(协议,如 http/https)与目标配置不一致时,向客户端返回重定向响应。文中将以当前仓库 Traefik v3 为对象,覆盖多种配置源(结构化 YAML/TOML、Docker/Swarm Labels、Marathon 等 Tags、Kubernetes CRD Middleware)的完整写法、scheme/permanent/port三个参数的精确语义,并结合 redirect_scheme.go 与 redirect_scheme_test.go 深入解析其如何依赖X-Forwarded-Proto判定 scheme、如何生成 301/302/307/308 响应以及默认端口自动归一化等底层原理。读完本文,你可以独立完成"全站 HTTP→HTTPS 强制跳转""多端口重定向"等生产级配置,并能准确预判在不同反向代理场景下的行为。
一、RedirectScheme 是什么
RedirectScheme是 Traefik HTTP 路由链中的一种重定向类中间件。它的核心职责非常简单:当客户端请求所使用的 scheme(协议)与配置中期望的 scheme 不同时,把请求重定向到期望的 scheme。
也就是说,它并不会把所有请求一律跳转,而是先做"协议是否一致"的判断:
- 请求已是目标 scheme(例如配置为
https且请求本身就是 HTTPS),则直接放行到下一个处理器; - 请求 scheme 与目标不一致(例如配置为
https但请求是 HTTP),则返回 3xx 重定向,并把Location指向 scheme 与(可选的)端口重写后的同路径 URL。
最典型的生产场景是全站强制 HTTPS:将监听在:80入口点的 HTTP 请求统一重定向到 HTTPS 入口点,从而避免明文传输。此外,它也能在多个协议端口共存的场景下(如 http:8080 与 https:8443)把流量引导到正确的端点。
从源码结构看,重定向中间件家族共包含两类成员,均位于 pkg/middlewares/redirect 目录:
| 中间件 | 依据 | 实现文件 |
|---|---|---|
RedirectScheme | 依据scheme / 端口 | redirect_scheme.go |
RedirectRegex | 依据正则表达式 | redirect_regex.go |
其中RedirectScheme在服务启动装配时由 pkg/server/middleware/middlewares.go#L322 调用redirect.NewRedirectScheme(ctx, next, *config.RedirectScheme, middlewareName)创建,属于动态配置中声明即生效的中间件。
二、部署前置条件:反向代理链中的信任关系
文档(见 redirectscheme.md)在正文开头就给出了一个重要的前置警告:
当 Traefik 前面还有至少一个反向代理时,这个"最后一跳"的反向代理必须被 Traefik 视为**可信(trusted)**来源。
原因在于 RedirectScheme 判断客户端实际使用的协议,依赖的是从上游转发的X-Forwarded系列请求头(详见下文"scheme 判定原理")。如果中间的代理不被信任,Traefik 会清理掉这最后一跳传入的X-Forwarded-Proto等转发头(以防止伪造),此时 RedirectScheme 便无从得知真实协议,重定向行为就会失效或产生错误结果。
如何把前置代理加入可信来源,请参阅入口点配置文档中的可信代理(Forwarded Headers / trusted IPs)相关小节:entrypoints.md 配置选项。
三、Configuration Examples:五种配置源完整示例
原文档给出了四段直接可用的配置示例(YAML、TOML、Labels、Tags),外加 Kubernetes CRD 写法,此处完整保留并补充说明每一种写法的适用 Provider。
1. 结构化配置(YAML)
适用于以静态文件、Docker/K8s 等 Provider 注入的动态配置:
# Redirect to https http: middlewares: test-redirectscheme: redirectScheme: scheme: https permanent: true2. 结构化配置(TOML)
# Redirect to https [http.middlewares] [http.middlewares.test-redirectscheme.redirectScheme] scheme = "https" permanent = true3. Docker / Swarm 容器标签(Labels)
Labels写法适用于 Docker、Docker Swarm Provider,在容器或服务上以traefik.http.middlewares.<name>.<option>=<value>形式声明:
# Redirect to https labels: - "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https" - "traefik.http.middlewares.test-redirectscheme.redirectscheme.permanent=true"4. Marathon 等 Tags
Tags写法适用于 Marathon、Rancher、Consul Catalog 等以键值 Tags 提供元数据的 Provider,键名规则与 Labels 相同:
// Redirect to https { // ... "Tags": [ "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https", "traefik.http.middlewares.test-redirectscheme.redirectscheme.permanent=true" ] }5. Kubernetes CRD Middleware
在 Kubernetes 中推荐通过自定义资源Middleware(traefik.io/v1alpha1)声明,再用注解挂载到 IngressRoute:
# Redirect to https apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: test-redirectscheme spec: redirectScheme: scheme: https permanent: true注意 K8s Labels 写法中 key 的大小写:容器标签场景下中间件名称段统一使用小写
redirectscheme,而 YAML/TOML/CRD 中字段使用驼峰redirectScheme。从源码看,Labels/Tags 这类键值对最终会通过 pkg/config/label 的解析器映射到结构化字段,因此键名遵循 Provider 的标签命名约定(traefik.http.middlewares.*),而 CRD 的spec.redirectScheme则由 pkg/provider/kubernetes/crd/kubernetes.go#L323 直接拷贝到动态配置结构体中。
6. 把中间件挂载到路由器上使用
声明中间件本身并不会生效,必须通过路由器的middlewares引用它才会进入请求处理链。以最常用的容器 Labels 为例,完整的"中间件 + 路由器 + HTTPS 入口点强制跳转"组合如下:
labels: # 1) 声明中间件 - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true" # 2) 在路由器上引用(example.com 的 HTTP 请求将被跳转到 https://example.com/...) - "traefik.http.routers.my-router.rule=Host(`example.com`)" - "traefik.http.routers.my-router.entrypoints=web" - "traefik.http.routers.my-router.middlewares=redirect-to-https"对 Kubernetes 的 IngressRoute 场景,则在routes[].middlewares中按名称引用已创建的Middleware资源。
四、Configuration Options:参数详解
原文档的参数表如下,这是该中间件全部对外暴露的配置项:
| Field | Description | Default | Required |
|---|---|---|---|
scheme | 新 URL 使用的协议(scheme) | "" | Yes |
permanent | 是否启用永久重定向(对应 301/308) | false | No |
port | 新 URL 使用的端口。注意必须传字符串,而不是数值 | "" | No |
三个参数的精确语义与取值约束说明如下。
scheme(必填)
- 目标协议,决定请求应被重定向到
http、https(源码常量定义于 redirect.go),测试用例还覆盖了wss作为目标的场景。 - 必填:构造函数中若
len(conf.Scheme) == 0,直接返回错误"you must provide a target scheme",中间件创建失败(见 redirect_scheme.go#L32-L34)。
permanent(可选,默认false)
控制重定向的"永久性",与返回状态码的映射关系如下(详见下文源码分析):
| permanent | 请求方法 | 返回状态码 |
|---|---|---|
false(临时) | GET | 302 Found |
false(临时) | 其他方法(如 POST) | 307 Temporary Redirect |
true(永久) | GET | 301 Moved Permanently |
true(永久) | 其他方法(如 POST) | 308 Permanent Redirect |
状态码会因请求方法不同而变化,这一点很容易被忽略:permanent对 GET 请求给出 301/302,对非 GET 请求则分别给出 308/307,以避免 301/302 被某些客户端改写为 GET 而破坏 POST 语义。
port(可选,默认"")
- 重定向目标 URL 的端口。文档特别强调:必须写成字符串(如
"8443"),而不是数值(如8443),这与动态配置字段声明为string类型一致。 - 若不设置,则目标 URL 保留原请求 Host 中的端口,并遵循默认端口归一化规则(当 http 配 80、https 配 443 时不携带端口,见下文)。
- 该端口还可用于"改端口"跳转,例如把
http://foo:8080永久重定向到https://foo:8443。
值得说明的内部细节:结构体 RedirectScheme 实际上还有第四个字段
ForcePermanentRedirect,但它在 YAML/TOML/Labels 等所有外部配置载体上均被标记为忽略(json/toml/yaml/label/file/kv 全部为"-"),仅作为内部字段供Kubernetes ingress-nginx Provider使用——当 Ingress 上开启 SSL 重定向注解时,translator.go#L396-L402 会构造RedirectScheme{Scheme: "https", ForcePermanentRedirect: true},强制所有方法一律返回 308(详见第五节状态码逻辑)。
五、源码级原理:请求如何被判定并重定向
1. 重定向目标 URL 的组装
NewRedirectScheme(redirect_scheme.go#L27-L57)在创建中间件时就固定了两件事:
- 目标替换模板为
conf.Scheme + "://" + ${2} + port + ${4}。这里的${2}与${4}引用的是正则捕获组; - 用于匹配原请求 URL 的正则
uriPattern:^(https?:\/\/)?(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$即依次捕获:协议前缀(
$1)、Host(支持 IPv6 字面量[...]与普通域名/IP,$2)、端口($3)、以及其余路径部分($4)。
其中端口归一化规则在此完成(redirect_scheme.go#L36-L39):
port := "" if len(conf.Port) > 0 && !(conf.Scheme == schemeHTTP && conf.Port == "80" || conf.Scheme == schemeHTTPS && conf.Port == "443") { port = ":" + conf.Port }含义是:scheme为http且port为80,或scheme为https且port为443时,该端口属于协议默认端口,会被省略、不写进目标 URL(即http→80、https→443都被视为"无端口")。例如配置{scheme: http, port: "80"},访问http://foo:80时新旧 URL 完全一致,中间件不会触发重定向——测试用例to HTTP 80与to HTTPS 443断言返回200 OK正是这一逻辑的验证。
2. 当前请求 scheme 的判定:X-Forwarded-Proto 是关键
中间件必须重建"客户端眼中看到的原始 URL",再与新目标比较。clientRequestURL(redirect_scheme.go#L59-L107)按照如下优先级确定 scheme:
- 从
req.RequestURI中解析显式携带的协议前缀; - 若
req.TLS != nil(Traefik 本地已终止 TLS),判定为https; - 若请求头存在
X-Forwarded-Proto,则以它为准,并做 WebSocket 语义归并(见下); - 最终还会把默认端口从 URL 中剥离(http:80 / https:443 不展示)。
关于第 3 步有一个很关键的实现细节。由于前一跳代理可能把连接升级场景(WebSocket)的协议写成ws/wss,而本中间件只在 HTTP(S) 语境中使用,因此 redirect_scheme.go#L87-L101 会做如下转换:
X-Forwarded-Proto为http或ws→ 按http处理;- 为
https或wss→ 按https处理; - 其他未知值 → 记录 Debug 日志
Invalid X-Forwarded-Proto并忽略,回落到原有判定。
这解释了第二节信任警告的必要性:如果 Traefik 不信任其前置代理并清除了X-Forwarded-Proto,那么真实协议将无法被感知。测试用例HTTP to HTTPS, with X-Forwarded-Proto to HTTPS与...to wss均断言不重定向、返回 200——因为携带的转发头已表明请求"实质上已是 https",无需再跳转,这能有效避免在代理链中形成无限重定向循环。
3. 状态码决策与重定向执行
真正的重定向执行位于通用实现 redirect.go。ServeHTTP的处理流程为:
- 通过
rawURL(req)(即上面的clientRequestURL)重建原始 URL; - 正则不匹配 → 直接放行到 next handler;
- 用
replacement模板做正则替换得到newURL; - 若
newURL != oldURL→ 交给moveHandler写Location头并返回对应状态码; - 若替换后与原来一致 → 原地把
req.URL替换为解析后的新 URL 并继续向后传递(这正是"已是目标 scheme 则放行"的落点)。
状态码决策逻辑见moveHandler.ServeHTTP(redirect.go#L92-L115),其规则与第四节参数表中的映射完全对应:
status := http.StatusFound // 302 if req.Method != http.MethodGet { status = http.StatusTemporaryRedirect // 307 } if m.permanent { status = http.StatusMovedPermanently // 301 if req.Method != http.MethodGet { status = http.StatusPermanentRedirect // 308 } } if m.statusCode != nil { // ForcePermanentRedirect 时强制 308 status = *m.statusCode }当ForcePermanentRedirect=true(即 ingress-nginx 的 SSL 重定向场景)时,permanentRedirectCode被固定为308 Permanent Redirect,无论 GET 还是其他方法都返回 308,以严格保留请求方法与请求体语义。测试用例HTTP to HTTPS with explicit 308 status code与...for GET request验证了这一行为。
六、行为边界与典型场景验证(测试用例归纳)
redirect_scheme_test.go 用约 30 组表格化用例固化了中间件行为,以下行为边界均有测试背书,可在实际排障时作为"预期行为清单":
| 场景 | 配置 | 期望行为 |
|---|---|---|
缺省scheme | {} | 创建中间件报错,handler 为 nil |
| HTTP→HTTPS | scheme: https | 302,Location: https://foo |
| 已是 HTTPS | scheme: https,请求带X-Forwarded-Proto: https | 不重定向,200 |
代理头为ws/未知值 | scheme: https,X-Forwarded-Proto: ws/bar | 判定为 http →302跳 https |
代理头为wss | scheme: https,X-Forwarded-Proto: wss | 判定为 https → 不重定向 |
| 改端口跳转 | scheme: https, port: "8443" | http://foo:8000→https://foo:8443(302) |
| 永久重定向 | scheme: https, port: "8443", permanent: true | http://foo→https://foo:8443(301) |
| 默认端口归一 | scheme: http, port: "80"(或 https/443) | 与现 URL 相同 → 不重定向,200 |
| IPv6 支持 | scheme: https | http://[::1]:80→https://[::1](保留方括号与地址) |
| WebSocket 目标 | scheme: wss, port: "9443" | http://foo→wss://foo:9443(302) |
几点从用例中可以总结出的生产经验:
- 不要担心"多一跳代理后死循环":只要前置代理正确设置
X-Forwarded-Proto: https,即便用户通过 http URL 进入,只要 Traefik 接收到的已是 TLS 连接且转发头为 https,中间件也会放行;但反过来,若信任配置缺失导致转发头被清理,就会退化为不断跳转。 - 端口跳转时会改写原端口:
https://foo:8000在scheme: https(不带 port)的配置下会被归一为https://foo,即目标 URL 的端口不是"自动沿用原端口",而是按配置(或默认端口剥离)重算。 - URL 中其他部分(路径、查询串)保持不变:重定向只作用于 scheme/host/port 前缀,正则
$4捕获的路径部分被原样拼接回目标 URL。 - IPv6 地址能够正确处理:正则中的
\[[\w:.]+\]分支专门处理[::1]这类字面量,跳转后不会丢失方括号。
七、与本仓库其他能力的关系与延伸阅读
- 若你需要比 scheme 更复杂的重写规则(例如同时改写路径、host),应使用同一家族的 RedirectRegex 中间件,其实现位于 redirect_regex.go。
- RedirectScheme 的响应不会被再次套娃处理:它属于服务端返回的 3xx 响应,若你希望搭配"错误页/自定义响应体",可结合 customerrors 等机制,但最典型的组合仍是"HTTPS 入口点只挂 RedirectScheme,真实服务放在 HTTPS 路由上"。
- 想让 Traefik 自动签发证书配合本中间件实现"零人工配置"的 HTTPS,可结合 Let's Encrypt 配置 与 HTTPS 路由相关文档使用。
- 关于中间件在请求链中的顺序编排、以及
chain中间件把多个中间件打包复用,可阅读 HTTP 中间件 overview。
综上,RedirectScheme虽是一个"小而专"的中间件,但其对X-Forwarded-Proto的依赖、对 301/302/307/308 的方法感知选择、对默认端口的归一化处理,共同决定了它在反向代理拓扑与 Kubernetes 场景下的精确行为。把握上述源码级细节,即可在生产中写出可靠且不会产生重定向风暴的强制 HTTPS 规则。
【免费下载链接】traefikThe Cloud Native Application Proxy项目地址: https://gitcode.com/GitHub_Trending/tr/traefik
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考