它是一个面向 Traefik 和 Nginx 的极简 ForwardAuth 开源鉴权网关,负责登录会话与请求访问控制,让你的后端服务哪怕没有写认证代码,也能被统一保护起来。
写在前面
几个月前,为了解决内部服务、工具面板、多个域名应用之间反复处理“登录”和“访问控制”的问题,我把前几年折腾反向代理、内部服务和认证入口的经验,整理成了一个开源小工具: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 判断这个请求已经通过认证,就返回通过,反向代理继续把请求转发给后端服务。

如果 Stargate 判断这个请求还没有登录,就不会让它直接进入后端服务,而是跳转到登录页,或者对 API 请求返回 401 Unauthorized。
用一张简单的流程图表示,大概是这样:
用户访问内部服务
↓
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 替换成自己的网络名即可。
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 自己的配置:
environment:
- AUTH_HOST=auth.test.localhost
- PASSWORDS=plaintext:test1234|test1337
- LANGUAGE=zh
AUTH_HOST 表示认证服务自己的域名。
PASSWORDS 表示可以用来登录的口令。
这里使用的是 plaintext,也就是明文口令,适合本地测试和快速体验。
真实环境里不要这样用,后面我们会再说。
第二部分,是把 Stargate 暴露给 Traefik:
- 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 中间件:
- traefik.http.middlewares.stargate.forwardauth.address=http://stargate/_auth
后面其他服务只要挂上这个中间件,请求就会先经过 Stargate 检查。
启动服务:
docker compose up -d
启动后,可以先访问:
http://auth.test.localhost/_login?callback=localhost
如果能看到登录页,说明 Stargate 已经跑起来了。
这一步完成后,它还只是一个认证服务。
接下来,我们需要把它挂到真正要保护的后端服务上。
接入一个真实服务
Stargate 跑起来之后,下一步就是把它挂到真正要保护的服务上。
这里用 traefik/whoami 做例子。
whoami 是一个很适合测试反向代理配置的小服务。访问它时,它会把请求信息原样展示出来,方便我们确认 Traefik 路由和中间件是否生效。
示例配置如下:
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
这里最关键的是这一行:
- traefik.http.routers.whoami.middlewares=stargate
它表示:访问 whoami.test.localhost 时,请求不要直接进入 whoami 服务,而是先经过前面定义好的 stargate 中间件。
也就是说,访问链路会变成:
用户访问 whoami.test.localhost
↓
Traefik 收到请求
↓
Traefik 调用 Stargate 的 /_auth
↓
Stargate 判断是否已经登录
↓
已登录:进入 whoami
未登录:跳转 Stargate 登录页
启动服务:
docker compose up -d
这里需要注意,如果你把 Stargate 和 whoami 写在同一个 compose.yaml 里,执行一次 docker compose up -d 即可。如果分成两个目录,则分别在对应目录执行。
然后访问:
http://whoami.test.localhost
如果你还没有登录,会先看到 Stargate 的登录页面。
输入前面配置的测试口令:test1234,登录完成后,再回到 whoami.test.localhost,就能看到 whoami 返回的请求信息。
到这里,一个最小的认证入口就跑通了。
后端服务没有改代码,也没有自己实现登录逻辑,只是因为在 Traefik 上挂了一个中间件,就被 Stargate 保护起来了。
常用配置说明
Stargate 的配置方式比较直接,主要通过环境变量完成。
第一次使用时,不需要把所有配置都看完。先理解下面几个最常用的就够了。
AUTH_HOST:认证服务在哪里
AUTH_HOST 表示 Stargate 自己的访问域名。
比如:
AUTH_HOST=auth.example.com
它告诉 Stargate:认证入口应该运行在哪个域名下。
当用户访问被保护的服务但还没有登录时,请求会被引导到这个认证域名下完成登录。
所以一般来说,你会准备一个单独的认证域名,比如:
auth.example.com
然后把其他服务放在类似下面的域名上:
grafana.example.com
whoami.example.com
docs.example.com
这样结构会比较清楚:
auth.example.com 负责登录
grafana.example.com 被保护的服务
whoami.example.com 被保护的服务
docs.example.com 被保护的服务
PASSWORDS:用什么口令登录
PASSWORDS 用来配置可以登录 Stargate 的口令。
它的格式是:
algorithm:password1|password2|password3
前面的 algorithm 表示口令使用什么算法,后面是具体口令,多个口令之间用 | 分隔。
例如:
PASSWORDS=plaintext:test123|admin456
这表示允许两个口令登录:
test123
admin456
其中 plaintext 表示明文口令。
它很适合本地测试,因为直观、方便、好排查问题。
但真实环境里不要使用明文口令。
生产环境建议使用更安全的方式,比如:
PASSWORDS=bcrypt:<your-bcrypt-hash>
或者:
PASSWORDS=sha512:<your-sha512-hash>
我个人比较建议先用 plaintext 在本地或测试环境把链路跑通。
确认 Traefik 路由、登录跳转、Cookie、后端服务访问都没问题之后,再把口令算法换成更适合生产环境的配置。
还是那句话:先把门装上,再把门锁换好。
COOKIE_DOMAIN:多域名如何共享登录态
如果你只保护一个服务,通常不需要太关心 COOKIE_DOMAIN。
但如果你有多个子域名,比如:
auth.example.com
grafana.example.com
whoami.example.com
docs.example.com
你大概率不希望每访问一个服务都重新登录一次。
这时可以配置:
COOKIE_DOMAIN=.example.com
这样 Stargate 设置的会话 Cookie 就可以在 *.example.com 之间共享。
用户只需要在 auth.example.com 登录一次,后续访问 grafana.example.com、whoami.example.com、docs.example.com 时,就可以复用这次登录状态。
这对 HomeLab、多内部面板、多工具站点的场景很有用。
登录页和请求头的几个可选配置
除了上面三个核心配置,Stargate 也提供了一些轻量的定制项。
比如界面语言:
LANGUAGE=zh
登录页标题:
LOGIN_PAGE_TITLE=我的认证服务
登录页页脚:
LOGIN_PAGE_FOOTER_TEXT=©2026 Your Company
认证成功后,Stargate 也可以给后端服务写入用户相关请求头。默认情况下,会使用类似这样的请求头:
X-Forwarded-User: authenticated
如果你的后端服务需要感知“这个请求已经通过入口认证”,可以读取这类请求头。不过要注意:后端服务应该只信任来自反向代理的请求。不要让用户绕过 Traefik 直接访问后端服务,否则这些请求头就失去了意义。
这也是前面反复强调“统一入口”的原因。
在 Traefik ForwardAuth 场景下,如果希望认证服务返回的用户头传递给后端服务,需要在 Traefik middleware 上显式配置要转发哪些响应头,比如:
- traefik.http.middlewares.stargate.forwardauth.authResponseHeaders=X-Forwarded-User
如果想暴露更多的 Stargate 返回的用户信息,比如邮箱、用户 ID,也可以扩展成:
- traefik.http.middlewares.stargate.forwardauth.authResponseHeaders=X-Forwarded-User,X-Forwarded-Email,X-Forwarded-User-Id
真实使用时的几个建议
如果你准备在自己的环境里使用 Stargate,我建议不要一上来就全量切换。
基础设施类工具最怕的不是“能力不够”,而是第一步就铺得太大。
一开始就把所有服务、所有域名、所有高级配置都接进来,看起来很完整,但一旦出了问题,很难判断到底是路由问题、Cookie 问题、认证问题,还是后端服务自己的问题。
所以我更建议小步接入。
第一步,先选一个不那么敏感的内部工具。
比如测试面板、文档站、开发环境 Dashboard,或者像前面一样用 whoami 做验证。
这个阶段只需要确认几件事:
- Traefik 路由是否正确;
- Stargate 登录页是否能打开;
- 未登录时是否会跳转;
- 登录后是否能回到原服务;
- 后端服务是否只能通过反向代理访问。
先把这条最短链路跑通,比一上来追求完整更重要。
第二步,再接入多个子域名。
当单个服务跑通之后,再把多个内部服务接进来。
比如:
grafana.example.com
docs.example.com
whoami.example.com
这时再配置:
COOKIE_DOMAIN=.example.com
重点验证多域名之间的登录态是否符合预期。
也就是:登录一次之后,访问其他子域名时,是否还需要重复登录。
第三步,把测试口令换成生产口令。
开发环境里使用 plaintext 很方便,配置直观,也容易排查问题。
但真实环境里不要使用明文口令。
等路由、跳转、Cookie 都确认没问题之后,再把口令配置换成更安全的形式,比如:
PASSWORDS=bcrypt:<your-bcrypt-hash>
或者:
PASSWORDS=sha512:<your-sha512-hash>
这一步很像换锁。
门已经装好了,接下来把临时锁换成更结实的锁。
第四步,再考虑多实例、审计和监控。
如果 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
星门启闭,守望验身,鸦使送信。
下一篇系列文章,让我们聊聊“守望者”。
–EOF