为什么统一认证登录成功了,另一个子域还是未登录
背景
做多站点自动登录时遇到一个很难查的现象:明明在一个域名下登录成功了,页面也显示昵称和积分,但切到同属一个集团的另一个子域,就又被当成未登录。
一开始的怀疑对象是 cookie 作用域、SameSite、或者某个子域单独维护了会话。逐个排掉之后,问题指向登录 URL 上的那个不起眼的查询参数:service。
现象
统一认证站点的登录地址形如:
https://auth.example.com/authui/login?service=<回调地址>&locale=zh-cn
用 service=https://a.example.com/home 登录后:
a.example.com正常,能读到用户信息;- 访问
b.example.com时浏览器直接被重定向到登录页,参数还是一模一样的那串。
看起来就像登录态完全没共享。
定位
关键是看回调时服务端下发的票据,而不是看页面上有没有昵称。登录成功后 URL 会带一个票据参数:
https://a.example.com/home?ticket=ST-8584543-xxxxxxxx
这个 ticket 是登录站点按 service 声明的目标地址签发的。签发范围就是 service 指定的那一个地址(含其所在域),不会自动扩展到兄弟子域。
验证方法很简单:把 service 换成另一个子域的地址重新登录,再回头看它是否也拿到了自己的 ticket。两次都能拿到,说明问题不在账号,而在第一次登录的 service 覆盖范围不够。
再进一步,用 document.cookie 观察:登录站点写下的会话 cookie 确实在 auth.example.com 上,兄弟子域能带上它(父域 cookie),但带着这个 cookie 访问 b.example.com 时,那一侧仍会去校验「有没有为本站签发的票据」,没有就重定向。所以「cookie 带着了」和「这一侧认为你登录了」是两件事。
根因
统一认证的会话有两层:
- 认证中心自己的会话(cookie 存在 auth 域上,通常对整个父域可见);
- 各业务站点的本地会话,由认证中心按
service签发的票据在回调时建立。
第一层是「你是谁」,第二层是「这个站点确认过你是谁」。只完成第一层,业务站点仍然会把你当成未登录,直到它收到一次属于自己地址的票据。
而很多业务站点的登录链接是硬编码了 service 的,只是拼了当前页面地址。如果你从一个站点跳到另一个站点,用的是前者的登录链接,票据就签给了前者。
修法
把 service 指向一个能被所有目标站点共享的落地地址,而不是某一个具体业务页:
https://auth.example.com/authui/login?service=https%3A%2F%2Fportal.example.com%2Findex&locale=zh-cn
实测这一改动之后,登录一次,社区、论坛、博客、认证中心四个子域不再各自拦一遍。反过来,如果 service 写的是某个具体业务页(例如 a.example.com/grow),那么只有那一个页面及其同域站点算登录,其它子域在首次访问时仍会跳一次登录。
排查顺序(可直接照做)
- 先看登录成功后回调地址上的
ticket参数,确认票据是签给哪个域的; - 在目标子域里再访问一次,看是否被重定向到登录页、
service是否被改写; - 对比两次
service的差异,判断是覆盖范围问题还是该子域真的没登录; - 确认 cookie 存在哪个域上,不要把「cookie 在」等同于「这个站点已登录」。
小结
三点:
- 统一认证的
service参数不是装饰,它决定票据签发给谁; - 「认证中心有会话」与「业务站点已登录」是两层状态,前者不自动带来后者;
- 多子域场景下,把
service指向公共落地页能一次性覆盖,而指向具体页只能覆盖那一个域。
- 点赞
- 收藏
- 关注作者
评论(0)