本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2026年05月30日 统计字数: 11146字 阅读时间: 23分钟阅读 本文链接: https://soulteary.com/2026/05/30/stargate-lightweight-forward-auth-gateway-for-internal-services.html ----- # Stargate(星空之门):不用改业务代码,给内部服务加一道登录门 它是一个面向 Traefik 和 Nginx 的极简 ForwardAuth 开源鉴权网关,负责登录会话与请求访问控制,让你的后端服务哪怕没有写认证代码,也能被统一保护起来。 ## 写在前面 几个月前,为了解决内部服务、工具面板、多个域名应用之间反复处理“登录”和“访问控制”的问题,我把前几年折腾反向代理、内部服务和认证入口的经验,整理成了一个开源小工具:Stargate(星空之门)。 它的目标很简单:**把认证逻辑前移到网关侧,让反向代理先替你拦住请求,而不是让每个业务服务都重新写一套登录、鉴权、跳转和会话判断。** ![soulteary/stargate](https://attachment.soulteary.com/2026/05/30/github.jpg) 开源项目地址在:[https://github.com/soulteary/stargate](https://github.com/soulteary/stargate),如果你觉得还不错,欢迎“一键三连”,顺手点个 Star。 如果你手里有一堆内部面板、测试服务、临时工具站点,或者 HomeLab 里的各种小服务,那么这类问题大概率并不陌生。 一开始,我们往往只是想“先跑起来”。 但服务越来越多之后,真正麻烦的事情就来了。 ### 问题是怎么出现的 一开始,问题通常并不明显。 你可能只是有一个监控面板、一个测试后台、一个临时文档站,或者一个内部 API 调试页。它们都在内网里,访问的人也不多,于是很容易先裸奔。 这在早期很正常。 毕竟,比起搭一套登录系统,我们更关心的是服务能不能跑、功能能不能用、问题能不能先解决。 但随着时间往后走,服务会越来越多,域名会越来越多,访问的人也会越来越多。这个时候,“先裸奔一下”就会慢慢变成一个长期存在的隐患。 常见的问题包括: - 每个服务都单独做登录,重复劳动很多; - 有些服务是第三方工具,根本不好改代码; - 多个域名之间反复登录,体验很差; - 临时口令散落在不同服务里,维护起来很麻烦; - 想接完整 SSO,但眼前只是想先把门装上; - 明知道应该加认证,但又不想为了一个小工具引入一套大系统。 所以很多时候,我们并不是缺一个登录页面。 **我们缺的是一个能够稳定工作在入口处的认证闸门。它不需要特别复杂,但要足够好用。** 它最好能放在反向代理后面,业务服务前面;先替我们把未认证的请求拦下来,再把通过认证的请求放进去。 这就是我做 Stargate 的出发点。 ### 以前通常怎么解决 遇到这类问题,常见做法大概有几种。 **第一种,是每个服务自己写登录。** 如果只有一个应用,这样当然没问题。用户体系本来就是业务的一部分,登录、注册、权限、会话都放在应用里,是很自然的事情。 但内部工具和业务系统不太一样。 很多时候,它们只是一些监控面板、调试页面、临时后台、文档站或者第三方工具。为了这些服务分别实现一套登录逻辑,不仅麻烦,而且后面维护起来也很碎。 登录页风格不统一,口令规则不统一,会话过期逻辑不统一,API 请求和浏览器请求的处理方式也不统一。 等服务数量多起来之后,这些“小麻烦”就会变成长期负担。 **第二种,是继续裸奔。** 开发环境里这样做问题不大。服务只在本机跑,或者只给自己临时用一下,确实没必要一开始就把认证体系做得很重。 但如果这些服务开始暴露到公网,或者团队里越来越多人一起使用,再继续裸奔就不太合适了。 很多安全问题不是一开始就爆发的,而是从“这个服务应该没人知道吧”“这个地址应该没人会访问吧”开始的。 **第三种,是直接接入完整的 SSO 或 IAM 系统。** 比如 Keycloak、Authentik、Authelia 这类方案,能力都很完整,可以处理用户、权限、MFA、OIDC、SAML、策略控制等一整套问题。 如果你要做的是企业级身份治理,这些方案当然更合适。 但对很多个人项目、小团队内部工具、HomeLab 或临时服务来说,它们又显得有点重。 你可能只是想给几个服务先加一道门,并不想第一天就把完整身份中台、数据库、邮件服务、回调地址、证书、策略规则全都配起来。 **第四种,是使用 OAuth2 Proxy 这类认证代理。** 如果你已经有 GitHub、Google、企业 IdP 或公司 SSO 作为统一登录入口,这类方案很好。它们可以把第三方身份提供方接到反向代理前面,让服务复用已有登录体系。 但如果你的需求更简单:只是想先保护一组内部服务,不想依赖外部 IdP,也不想为了一个工具站点配置一长串 OAuth 回调和 client secret,那么它仍然会有一些额外成本。 **所以,我想要的是一个更轻的中间方案。** - 它**不需要**改业务代码。 - 它**不要求**你一开始就有完整身份系统。 - 它**不强制**依赖第三方 IdP。 - 它**只**先解决一个问题:**在入口处,把未认证的请求拦下来。** ### 为什么我做了 Stargate 我希望 Stargate 先做一件很小、但很常见的事情:**把认证变成入口能力。** 它不是完整身份中台,也不试图替代企业里的 IAM / SSO 系统。它更像**一块基础设施胶水**,放在反向代理后面,接在业务服务前面,用尽可能低的部署成本,先把一组服务保护起来。 所以 Stargate 的取舍很明确。 - 它应该足够轻,最好一个服务就能跑起来。 - 它应该足够直接,不需要复杂配置文件,主要通过环境变量完成配置。 - 它应该能独立工作,即使你还没有 Warden、Herald、SSO 或其他外部身份系统,也能先用密码认证把入口保护起来。 - 它应该能和 Traefik 这类反向代理自然配合,不要求业务服务改代码。 - 它也应该给后续增强留出空间,比如多域名登录态、白名单、验证码、TOTP、Redis 会话、审计日志和监控指标。 但这些增强能力,不应该成为第一天使用它的门槛。 这也是我在做 Stargate 时比较在意的一点:**先把门装上,再把门锁换好。** 很多工具之所以最后没有被用起来,不是因为能力不够,而是因为第一步太重。 **Stargate 想反过来,先让第一步足够轻。** 它的起点应该是轻的:先让你能用很少的配置,把一个服务保护起来。等它真的成为入口的一部分,再逐步增加更强的认证能力和生产部署能力。 对很多内部工具、测试服务和 HomeLab 场景来说,这种节奏会更舒服。 ### Stargate 是怎么工作的 Stargate 使用的是 Forward Auth 模式。 这个模式名字听起来有点抽象,但它的工作方式其实很简单。正常情况下,用户访问一个服务时,请求会直接经过反向代理,再进入后端服务。 加入 Stargate 之后,反向代理会先多问一步: > 这个请求已经登录了吗?可以放行吗? 如果 Stargate 判断这个请求已经通过认证,就返回通过,反向代理继续把请求转发给后端服务。 ![一个基础的验证页面](https://attachment.soulteary.com/2026/05/30/webui.jpg) 如果 Stargate 判断这个请求还没有登录,就不会让它直接进入后端服务,而是跳转到登录页,或者对 API 请求返回 `401 Unauthorized`。 用一张简单的流程图表示,大概是这样: ```Text 用户访问内部服务 ↓ Traefik 收到请求 ↓ Traefik 先请求 Stargate 的 /_auth ↓ Stargate 检查登录状态 ↓ 已登录:返回 200,Traefik 放行请求 未登录:跳转登录页,或者返回 401 ``` 这个过程里, - 后端服务并**不需要**知道“登录”这件事。 - 它**不需要**实现登录页面。 - **不需要**处理会话。 - **不需要**判断用户是否已经认证。 它**只需要**继续提供自己的业务能力,入口处的 Stargate 会负责把未认证的请求挡在外面。 这也是 Stargate 最适合解决的问题:**不改业务代码,先在入口处加一道门。** 当然,这里有一个前提:你的服务访问路径应该统一经过反向代理。如果用户可以绕过 Traefik 或 Nginx,直接访问后端服务本身,那 Stargate 就拦不住它。 所以 Stargate 更适合放在“统一入口”已经存在,或者你准备整理统一入口的环境里。 ## 用 Docker Compose 快速跑起来 理解了工作方式之后,我们先把 Stargate 跑起来。 下面的示例使用示例域名是 `auth.test.localhost` 和 `whoami.test.localhost`。如果你的环境不能自动解析这些域名,需要提前在 DNS 或 `/etc/hosts` 中把它们指向 Traefik 所在机器。 Stargate 的最小运行配置并不复杂,核心只需要两个配置: - `AUTH_HOST`:认证服务自己的访问域名; - `PASSWORDS`:允许登录的口令。 下面是一个最小化示例。 这里假设你已经有一个 Traefik 网络,名字叫 `proxy`。如果你的网络名字不同,把下面的 `proxy` 替换成自己的网络名即可。 ```yaml services: stargate: image: soulteary/stargate:latest container_name: stargate restart: unless-stopped environment: - AUTH_HOST=auth.test.localhost - PASSWORDS=plaintext:test1234|test1337 - LANGUAGE=zh networks: - proxy labels: - traefik.enable=true - traefik.docker.network=proxy - traefik.http.routers.stargate.entrypoints=http - traefik.http.routers.stargate.rule=Host(`auth.test.localhost`) || Path(`/_session_exchange`) - traefik.http.routers.stargate.service=stargate - traefik.http.services.stargate.loadbalancer.server.port=80 - traefik.http.middlewares.stargate.forwardauth.address=http://stargate/_auth - traefik.http.middlewares.stargate.forwardauth.trustForwardHeader=true networks: proxy: external: true ``` 这段配置里,真正需要先看懂的是三部分。 **第一部分,是 Stargate 自己的配置:** ```yaml environment: - AUTH_HOST=auth.test.localhost - PASSWORDS=plaintext:test1234|test1337 - LANGUAGE=zh ``` `AUTH_HOST` 表示认证服务自己的域名。 `PASSWORDS` 表示可以用来登录的口令。 这里使用的是 `plaintext`,也就是明文口令,适合本地测试和快速体验。 真实环境里不要这样用,后面我们会再说。 **第二部分,是把 Stargate 暴露给 Traefik:** ```yaml - traefik.http.routers.stargate.rule=Host(`auth.test.localhost`) || Path(`/_session_exchange`) - traefik.http.services.stargate.loadbalancer.server.port=80 ``` 这表示当用户访问 `auth.test.localhost` 时,会进入 Stargate 的登录页面和认证相关接口。(这里额外匹配 `/_session_exchange`,是为了后续多域名登录态交换使用。第一次只跑单域名体验时,可以先不深究它。) **第三部分,是定义一个叫 stargate 的 ForwardAuth 中间件:** ```yaml - traefik.http.middlewares.stargate.forwardauth.address=http://stargate/_auth ``` 后面其他服务只要挂上这个中间件,请求就会先经过 Stargate 检查。 启动服务: ```bash docker compose up -d ``` 启动后,可以先访问: ```yaml http://auth.test.localhost/_login?callback=localhost ``` 如果能看到登录页,说明 Stargate 已经跑起来了。 这一步完成后,它还只是一个认证服务。 接下来,我们需要把它挂到真正要保护的后端服务上。 ## 接入一个真实服务 Stargate 跑起来之后,下一步就是把它挂到真正要保护的服务上。 这里用 `traefik/whoami` 做例子。 `whoami` 是一个很适合测试反向代理配置的小服务。访问它时,它会把请求信息原样展示出来,方便我们确认 Traefik 路由和中间件是否生效。 示例配置如下: ```yaml services: whoami: image: traefik/whoami:v1.11 container_name: whoami restart: unless-stopped networks: - proxy labels: - traefik.enable=true - traefik.docker.network=proxy - traefik.http.routers.whoami.entrypoints=http - traefik.http.routers.whoami.rule=Host(`whoami.test.localhost`) - traefik.http.routers.whoami.service=whoami - traefik.http.services.whoami.loadbalancer.server.port=80 - traefik.http.routers.whoami.middlewares=stargate networks: proxy: external: true ``` 这里最关键的是这一行: ```yaml - traefik.http.routers.whoami.middlewares=stargate ``` 它表示:访问 `whoami.test.localhost` 时,请求不要直接进入 whoami 服务,而是先经过前面定义好的 stargate 中间件。 也就是说,访问链路会变成: ```Text 用户访问 whoami.test.localhost ↓ Traefik 收到请求 ↓ Traefik 调用 Stargate 的 /_auth ↓ Stargate 判断是否已经登录 ↓ 已登录:进入 whoami 未登录:跳转 Stargate 登录页 ``` 启动服务: ```bash docker compose up -d ``` 这里需要注意,如果你把 Stargate 和 whoami 写在同一个 `compose.yaml` 里,执行一次 `docker compose up -d` 即可。如果分成两个目录,则分别在对应目录执行。 然后访问: ```Text http://whoami.test.localhost ``` 如果你还没有登录,会先看到 Stargate 的登录页面。 输入前面配置的测试口令:`test1234`,登录完成后,再回到 `whoami.test.localhost`,就能看到 whoami 返回的请求信息。 到这里,一个最小的认证入口就跑通了。 后端服务没有改代码,也没有自己实现登录逻辑,只是因为在 Traefik 上挂了一个中间件,就被 Stargate 保护起来了。 ## 常用配置说明 Stargate 的配置方式比较直接,主要通过环境变量完成。 第一次使用时,不需要把所有配置都看完。先理解下面几个最常用的就够了。 ### AUTH\_HOST:认证服务在哪里 `AUTH_HOST` 表示 Stargate 自己的访问域名。 比如: ```bash AUTH_HOST=auth.example.com ``` 它告诉 Stargate:认证入口应该运行在哪个域名下。 当用户访问被保护的服务但还没有登录时,请求会被引导到这个认证域名下完成登录。 所以一般来说,你会准备一个单独的认证域名,比如: ```Text auth.example.com ``` 然后把其他服务放在类似下面的域名上: ```Text grafana.example.com whoami.example.com docs.example.com ``` 这样结构会比较清楚: ```Text auth.example.com 负责登录 grafana.example.com 被保护的服务 whoami.example.com 被保护的服务 docs.example.com 被保护的服务 ``` ### PASSWORDS:用什么口令登录 `PASSWORDS` 用来配置可以登录 Stargate 的口令。 它的格式是: ```bash algorithm:password1|password2|password3 ``` 前面的 `algorithm` 表示口令使用什么算法,后面是具体口令,多个口令之间用 `|` 分隔。 例如: ```bash PASSWORDS=plaintext:test123|admin456 ``` 这表示允许两个口令登录: ```bash test123 admin456 ``` 其中 `plaintext` 表示明文口令。 它很适合本地测试,因为直观、方便、好排查问题。 但真实环境里不要使用明文口令。 生产环境建议使用更安全的方式,比如: ```bash PASSWORDS=bcrypt: ``` 或者: ```bash PASSWORDS=sha512: ``` 我个人比较建议先用 `plaintext` 在本地或测试环境把链路跑通。 确认 Traefik 路由、登录跳转、Cookie、后端服务访问都没问题之后,再把口令算法换成更适合生产环境的配置。 还是那句话:**先把门装上,再把门锁换好。** ### COOKIE\_DOMAIN:多域名如何共享登录态 如果你只保护一个服务,通常不需要太关心 `COOKIE_DOMAIN`。 但如果你有多个子域名,比如: ```bash auth.example.com grafana.example.com whoami.example.com docs.example.com ``` 你大概率不希望每访问一个服务都重新登录一次。 这时可以配置: ```bash COOKIE_DOMAIN=.example.com ``` 这样 Stargate 设置的会话 Cookie 就可以在 `*.example.com` 之间共享。 用户只需要在 `auth.example.com` 登录一次,后续访问 `grafana.example.com`、`whoami.example.com`、`docs.example.com` 时,就可以复用这次登录状态。 这对 HomeLab、多内部面板、多工具站点的场景很有用。 ### 登录页和请求头的几个可选配置 除了上面三个核心配置,Stargate 也提供了一些轻量的定制项。 比如界面语言: ```bash LANGUAGE=zh ``` 登录页标题: ```bash LOGIN_PAGE_TITLE=我的认证服务 ``` 登录页页脚: ```bash LOGIN_PAGE_FOOTER_TEXT=©2026 Your Company ``` 认证成功后,Stargate 也可以给后端服务写入用户相关请求头。默认情况下,会使用类似这样的请求头: ```bash X-Forwarded-User: authenticated ``` 如果你的后端服务需要感知“这个请求已经通过入口认证”,可以读取这类请求头。不过要注意:后端服务应该只信任来自反向代理的请求。不要让用户绕过 Traefik 直接访问后端服务,否则这些请求头就失去了意义。 **这也是前面反复强调“统一入口”的原因。** 在 Traefik ForwardAuth 场景下,如果希望认证服务返回的用户头传递给后端服务,需要在 Traefik middleware 上显式配置要转发哪些响应头,比如: ```bash - traefik.http.middlewares.stargate.forwardauth.authResponseHeaders=X-Forwarded-User ``` 如果想暴露更多的 Stargate 返回的用户信息,比如邮箱、用户 ID,也可以扩展成: ```bash - traefik.http.middlewares.stargate.forwardauth.authResponseHeaders=X-Forwarded-User,X-Forwarded-Email,X-Forwarded-User-Id ``` ## 真实使用时的几个建议 如果你准备在自己的环境里使用 Stargate,我建议不要一上来就全量切换。 基础设施类工具最怕的不是“能力不够”,而是第一步就铺得太大。 一开始就把所有服务、所有域名、所有高级配置都接进来,看起来很完整,但一旦出了问题,很难判断到底是路由问题、Cookie 问题、认证问题,还是后端服务自己的问题。 所以我更建议小步接入。 **第一步,先选一个不那么敏感的内部工具。** 比如测试面板、文档站、开发环境 Dashboard,或者像前面一样用 `whoami` 做验证。 这个阶段只需要确认几件事: - Traefik 路由是否正确; - Stargate 登录页是否能打开; - 未登录时是否会跳转; - 登录后是否能回到原服务; - 后端服务是否只能通过反向代理访问。 先把这条最短链路跑通,比一上来追求完整更重要。 **第二步,再接入多个子域名。** 当单个服务跑通之后,再把多个内部服务接进来。 比如: ```bash grafana.example.com docs.example.com whoami.example.com ``` 这时再配置: ```bash COOKIE_DOMAIN=.example.com ``` 重点验证多域名之间的登录态是否符合预期。 也就是:登录一次之后,访问其他子域名时,是否还需要重复登录。 **第三步,把测试口令换成生产口令。** 开发环境里使用 `plaintext` 很方便,配置直观,也容易排查问题。 但真实环境里不要使用明文口令。 等路由、跳转、Cookie 都确认没问题之后,再把口令配置换成更安全的形式,比如: ```bash PASSWORDS=bcrypt: ``` 或者: ```bash PASSWORDS=sha512: ``` 这一步很像换锁。 门已经装好了,接下来把临时锁换成更结实的锁。 **第四步,再考虑多实例、审计和监控。** 如果 Stargate 只是保护少量内部服务,单实例已经足够简单好用。 但如果它开始保护多个系统,甚至变成一组服务的统一入口,就可以继续考虑: - 使用 Redis 保存会话,方便多实例共享登录状态; - 开启审计日志,记录关键认证行为; - 接入健康检查和指标监控,方便排查问题; - 限制 `/metrics` 等内部接口的访问范围。 这些能力很适合生产部署,但不必在第一天全部打开。 先让系统跑起来,再让系统跑得更稳。 **第五步,再考虑 Warden、Herald 和 TOTP。** 如果共享口令已经不够用了,再考虑把认证能力往上加。 比如用 Warden 管理用户白名单,用 Herald 发送验证码,再用 TOTP 支持 Authenticator 动态码。 这样登录流程就可以从“大家共用一个口令”,逐步升级成“每个人都有自己的身份和验证方式”。 这条路径会比一开始就引入完整身份系统轻很多。 小步接入,逐步增强,才容易排错,也容易建立信心。 ## 后续还能怎么增强 Stargate 可以完全独立运行,只使用密码认证。 这也是它最轻的一面。 你只需要一个认证域名、一组口令,再把中间件挂到后端服务上,就能先把内部服务保护起来。 但它并不是只能停留在共享口令阶段。 如果你希望它更接近生产环境,也可以继续往上加能力。 ### Warden:守望者 Warden 可以理解成 Stargate 后面的“准入名册”。 它负责回答一个问题: > 这个人是不是允许进来? 当你不想再依赖共享口令,而是希望根据用户邮箱、手机号或账号判断准入资格时,就可以把 Warden 接进来。 在这套体系里,Stargate 负责守入口,Warden 负责判断“谁可以进”。 ### Herald:鸦使 Herald 可以理解成负责送信的“鸦使”。 它负责验证码、OTP 和通知送达。 比如用户登录时,需要通过邮箱或短信收到验证码;或者某些验证流程需要发送一次性口令,这类事情就可以交给 Herald。 在这套体系里,Stargate 负责拦请求,Warden 负责认人,Herald 负责把验证信息送到用户手里。 ### TOTP:动态验证码 如果你希望支持 Authenticator 这类动态验证码,也可以继续接入 TOTP 能力。 这样用户可以先绑定动态验证码,再使用账号加 6 位动态码完成登录。 这比共享口令更适合多人协作场景。 ### Redis:多实例会话 默认情况下,小规模使用不需要引入额外依赖。 但如果你要运行多个 Stargate 实例,或者希望服务重启后登录状态不容易丢失,就可以使用 Redis 保存会话。 这样多个 Stargate 实例之间可以共享登录状态,后续做滚动更新或故障恢复也会更稳。 ### Audit Log 和 Metrics:可观测性 认证系统一旦变成入口,就不能只关注“能不能跑”。 还要关注: - 谁登录了; - 谁失败了; - 哪些请求被拦住了; - 验证码服务有没有异常; - 会话创建和销毁是否正常。 所以 Stargate 也可以继续接入审计日志和指标监控。 这些能力不是第一天必须开启的东西,但它们会让 Stargate 从“能用”变成“更适合长期运行”。 我更建议的节奏是: - 先用 Stargate 把门装上。 - 再用 Warden 管好谁能进。 - 再用 Herald 把验证码送出去。 - 最后,再补上 Redis、审计和监控,把它变成一套更完整的内部认证入口。 ## 最后 很多内部服务的安全问题,并不是因为大家不知道认证重要。 而是因为“给每个服务都写一遍登录”太麻烦,“一次性引入完整身份系统”又太重。 于是中间地带长期空着。 要么先裸奔,等以后再说。 要么为了一个不大的问题,引入一套很重的系统。 Stargate 想填的,就是这个中间地带。 它把认证前移到入口,把复杂度从业务代码里拿出来,把“每个应用重复实现登录”变成“接入反向代理的一项通用能力”。 这不是一个很宏大的目标。 但在真实的内部服务、测试环境、HomeLab 和小团队工具场景里,它能解决的问题很具体。 你可以先用最简单的密码认证把门装上。 等服务变多、使用的人变多、认证要求变高之后,再逐步接入 Warden、Herald、TOTP、Redis、审计日志和监控指标。 当你需要完整 IAM 时,应该选择完整 IAM。 当你只是想今天就把一堆内部站点先保护起来,Stargate 会是一个很顺手的选择。 如果这个项目对你有帮助,欢迎到 GitHub 顺手点个 Star:[https://github.com/soulteary/stargate](https://github.com/soulteary/stargate) > 星门启闭,守望验身,鸦使送信。 下一篇系列文章,让我们聊聊“守望者”。 --EOF