Authentik 从 2025.8 升级到 2026.8:那些没人提醒你会坏掉的部分

· 2 min read · devops authentikssodockerportainerself-hostingoidcupgrade

我把 authentik 当作自托管体系前面的单点登录闸门——邮箱、仪表盘、Synology 应用、远程访问 outpost 都在它后面。很长一段时间它停在 2025.8.3,一年的 版本更新堆积成山。这篇文章记录我是怎么一口气把它升到 2026.8.1 的,以及 沿途每一步坏掉的地方——尤其是我犯的那个让单点登录彻底瘫痪、逼每个内部 应用都重新要密码的错误。

什么是 authentik,为什么要用它?

authentik 是一个开源的身份提供商(IdP)——反复回答同一个问题的那个组件: 「这个人是谁,允许他进来吗?」 你可以把它想成整栋应用大楼的前门。与其让每个 应用都跑自己那套用户名/密码页面(还有自己那一堆 bug、自己的「忘记密码」流程、 自己的二次验证),不如让所有应用都来问 authentik。

它支持真实部署里真正用得上的协议:

实际的好处,按重要性排序:

  1. 一个登录通行所有。 登录一次,在每一个应用之间自由穿梭。用户不用再记一打 密码(也不用再陪着一堆由此而来的客服工单)。
  2. 一个地方收紧安全。 MFA、密码策略、会话限制、账户找回,都在 authentik 里 配一次——而不是每个应用各自重新实现一遍。
  3. 一个地方做审计。 每一个登录、每一个应用、每一个用户,都在同一个日志里。 出问题时,这就是「我们以为没事」和「我们有证据」之间的区别。
  4. 自包含、可自托管。 数据归你所有。没有按人头收费、随团队规模膨胀的 SaaS 费,也没有锁定——它是 AGPL 许可,跑在 Docker 里。

对一个运营着二十几个服务(邮箱、仪表盘、NAS 应用、内部工具)的小企业或独立 开发者来说,authentik 就是「每个应用各有一个脆弱密码」和「一道加固过的前门」之间 的区别。

为什么要升级

2025.8.3 本身没坏。但它落后太久,后面的版本里已经累积了一堆 CVE 漏洞修复, 而且我正准备做按应用区分品牌。authentik 自己的策略是不允许跨大版本跳级—— 它强制要求逐级走。所以计划是:

2025.8.3 → 2025.10 → 2025.12 → 2026.2 → 2026.5 → 2026.8

六跳,一次一步,每步之间跑一次迁移 + 一次健康检查。动手之前,有一个绝对 不能省的步骤:备份数据库。authentik 不支持降级。如果迁移只跑了一半, 你是从备份恢复,而不是回滚镜像 tag。

sudo docker exec authentik-postgres pg_dump -U authentik authentik > authentik-backup.sql

坑 1:Portainer 才是真相源,不是 compose 文件

我的第一反应是编辑磁盘上的 docker-compose.yml 然后 up。错的。这个 stack 是由 Portainer(stack 143)管理的,真正的 compose 和环境变量都保存在它 自己的存储里。磁盘上的 .env 早就过时了——它的 PG_PASS 跟 Portainer 实际 运行的对不上。

正确的更新路径是走 Portainer API,而不是文件系统:

  1. 更新 compose 内容里的镜像 tag。
  2. docker pull 新镜像(这样 API 调用不会在拉取中途超时)。
  3. docker stop + docker rm 正在运行的容器(否则固定的 container_name 会在重新部署时冲突)。
  4. 用新的 StackFileContent + EnvPUT /api/stacks/143?endpointId=2

我撞上了容器名冲突错误、拉取超时错误,还有一个网络连接问题——重建后的 authentik-server 只接入了它两个网络里的一个,导致无法解析 postgres-server。 一旦你看懂了门道,每个问题都是五分钟的修复;但凑在一起,耗掉了我大半个晚上。

坑 2:存储挂载迁移了(2025.12)

在 2025.12 之前,品牌资源放在 /media/ 下,通过 /media/... 提供服务。 2025.12 之后存储结构变了:文件迁移到 /data/media 结构,通过一个新的 /files/media/public/<name>?token=... URL(带 JWT 签名)提供服务。我的容器 还在挂载 ./media:/media,所以我一跨过那个版本,每个 logo、favicon、背景图 全部 404。

修复方式就是官方文档里的迁移:

mkdir -p data && mv media data/media

……然后把挂载改成 ./data:/data。新的文件后端还要求 /data 必须是一个真正的 挂载点才肯工作——我最开始的修复用的是符号链接,结果后端 is_mount() 检查 直接报错 No file management backend configured

坑 3:RBAC 迁移留下一张被污染的表

2025.12 为了 RBAC 重构,删除了旧的 authentik_core.User_groups 模型。迁移 本身跑得很干净,但旧会话仍然引用着这个已删除的模型。结果:登录页一直抛 LookupError: App 'authentik_core' doesn't have a 'User_groups' model

不是 django_session——那张表是空的。真正的元凶是 authentik 自己的 authentik_core_session 表。清空它(以及其他 session 表)强制所有人重新登录, 错误也随之消失:

TRUNCATE authentik_core_session;

有个副作用值得知道:这也会让你各应用持有的所有 OIDC refresh token 失效。它们 会把你弹去重新登录一次,然后恢复。这是一次性的麻烦,不是 bug。

坑 4:可信代理现在变成了显式选择(2026.8)

2026.8 收紧了默认的转发头处理。之前 authentik 信任所有内网段;现在它只信任 你显式列出的那些。在转发到 localhost 的 Synology 反向代理后面,意味着:

environment:
  AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS: 127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,::1/128

跳过这一步,代理头就会被拒绝,表现出来的认证失败症状,跟你实际上遇到的问题 长得几乎完全不像。

坑 5:那个搞坏 SSO 的——authorization_flow 和 authentication_flow 搞混

这是最贵的一个错误,而且如果你要做按应用区分品牌,特别容易搞混。

authentik 里的 provider 有两个 flow 字段,含义完全不同:

# "未登录用户访问关联应用时用的认证 flow"    ← 登录页
authentication_flow = models.ForeignKey(...)

# "授权这个 provider 时用的 flow"           ← OAuth 授权确认页
authorization_flow = models.ForeignKey(...)

我想要每个应用不同的背景图,于是给每个应用建了一个 flow,然后把 provider 的 authorization_flow 指向它。瞬间就坏了:一个已经认证的用户,每次打开另一个 应用,还是会被拖过完整的识别 → 密码 → MFA 流程。单点登录,实际上等于没了。

修复就是一条 UPDATE,把两个字段放回它们该在的位置:

UPDATE authentik_core_provider
SET authorization_flow_id = '1d85b1b1-...',   -- explicit-consent flow
    authentication_flow_id = 'aa##-per-app-flow'
WHERE ...;

而品牌本身——按应用区分的背景和标题——是放在 flow 自身backgroundtitle 字段上(2026.8 起可写),而不是放在 brand 的域名匹配上。

这个坑后面还藏着一个相关的坑。我创建那 18 个按应用区分的 flow 时,它们全都 落成 designation=authentication,而我的 auth.hoelee.com brand 的 flow_authenticationNULL。authentik 在一个 brand 没有显式认证 flow 时的 回退逻辑,是按 slug 字母序选第一个 authentication flow——碰巧就是 auth-agent,而不是 default-authentication-flow。于是根登录页开始显示我 agent 的背景图。把 brand 的 flow_authentication 设成真正的默认 flow 才修好。

还有一个跟 RBAC 坑叠加的问题:创建 flow 会设置它的 background,但不会设置 它的 stages——stages 在 flow 对象上是只读的。一个没有任何 stage 绑定的空 flow,正是迁移早期登录页无限重定向循环的罪魁祸首。stage 绑定是单独创建的:

POST /api/v3/flows/bindings/   # { target: "<flow pk>", stage: "<stage pk>", order: N }

我会怎么做得不一样

整场折腾归结为三个本可避免的模式:

  1. 绝不要靠猜字段的语义——我把 authorization_flow 当成了「登录 flow」, 而真相源是模型定义,字段自己的 docstring 里就把区别写得明明白白。
  2. 跟 API 表面保持距离——每次服务器重启,API token 都中途过期,结果关键 修复我直接用 psql 对着数据库做了。可靠,但应该在集群着火之前就脚本化, 而不是着火的时候。
  3. 每次重启只处理一个破坏性变更——我试图一口气推理存储、RBAC、代理的 全部变化。每一个如果单独隔离、独立验证,都会很简单。

结果

authentik 现在跑着 2026.8.1——最新、已打补丁、所有容器健康——18 个应用各自 在登录页显示自己的背景和标题,SSO 在每个子域都正常工作。我日常用的 17 个应用, 从「每次切换应用都要重新登录」回到了「登录一次,畅通无阻」。

值得记住的教训:认证基础设施的大版本升级,大概 10% 是「改镜像 tag」,90% 是 「数据模型、存储结构、代理规则在你脚下全都挪了位」。做好备份,一次走一个版本, 当某个东西表现得完全不合理时,伸手去抓下一份配置之前,先把字段名再念一遍。


想为你的企业搭一套单点登录?

如果你正在运营好几个内部应用——仪表盘、邮箱服务器、工单系统、文件服务器——而 你的团队还在一个个分开登录(或者到处重复用同一个密码),我搭设和维护的正是这类 基础设施。我会帮你部署 authentik、接到你现有的应用上、加上 MFA,确保「登录一次」 真的能跑通——最后还会给你一份交接文档,让你永远不会被锁死。

联系我:[email protected],或 WhatsApp +60 12-797 2969,看看我在 hoelee.com 做的东西。

Lee Teong Hoe

Full-stack developer & DevOps engineer. I build web apps, self-host infrastructure, and automate things — this blog is my living portfolio.