几个应用都能用同一个账号登录,演示通常很顺。用户实际点开的却可能是一台设备的详情,链接里还带着日期和筛选条件。认证后只回到首页,登录虽然成功,原来的工作却断了。
这次接入里,我把认证回调、本地会话和业务目标分别处理。它们发生在一条访问路径上,但不能共用一个未经约束的跳转参数。
回调地址和业务目标是两份信息
认证回调负责把认证结果交给注册过的应用;业务目标记录用户原来要打开什么。回调地址按协议匹配,目标地址由应用按自己的规则校验。不能让浏览器随意带一个外部地址,登录成功就原样跳过去。
应用服务端保存一次性登录事务,关联 state、nonce、发起会话、时间与业务目标。浏览器携带随机引用,回调校验并消费事务。采用 PKCE 时,本次 code_verifier 也与这份请求对应,不能在几次登录之间复用。
身份令牌需要验证签名、issuer、audience、有效期及约定的 nonce。解码出一份 JSON,并不代表身份可信。随后建立本站会话并轮换会话标识,再恢复获准的业务目标。认证失败或事务过期,就结束这次尝试,不把重复回调当成新的登录。
本地会话与业务权限各管什么
身份令牌用来确认用户身份,访问令牌面向目标接口,本站会话负责应用自己的登录状态。浏览器只持有合适的会话标识,服务端管理需要保留的令牌。Cookie 的 HttpOnly、Secure、SameSite 配置要结合实际回调方式验证。
用户登录成功,也可能没有设备详情的权限。这时应按资源隐藏策略显示无权限、资源不可用或申请入口,而不是反复送回登录页。否则用户只会以为账号输错了。
业务目标校验可以采用下面的受限形式。它只接受本站固定路径和已知展示参数,不能替代认证结果校验。
function safeBusinessTarget(raw, trustedOrigin) {
const fallback = "/";
try {
const url = new URL(raw, trustedOrigin);
if (url.origin !== trustedOrigin) return fallback;
if (url.username || url.password) return fallback;
if (!/^\/apps\/[a-z0-9_-]+\/pages\/[a-z0-9_-]+$/i.test(url.pathname)) {
return fallback;
}
const mode = url.searchParams.get("viewMode");
return url.pathname + (["0", "1", "2"].includes(mode)
? "?viewMode=" + mode : "");
} catch {
return fallback;
}
}trustedOrigin 来自可信配置,不能取同一请求里任意传入的值。目标即使通过校验,页面加载数据时仍要检查当前身份是否有权访问。
组织切换会使一批状态失效
用户在 A 组织发出查询,切到 B 后,A 的慢响应可能才回来。切换顶部名称不够,菜单、资源缓存和请求结果都要跟着变。前端可以提升 contextRevision,并让响应只更新相同版本的界面;可取消的旧请求同时取消,以减少无用工作。
服务端按已验证的会话和组织上下文限制查询。缓存键包含 tenant 与 organization,不能因为两个用户访问同一个页面就共用结果。下载与后台任务保留创建时的执行范围,切换组织不改变已经发起任务的含义。
嵌入页面隐藏导航、切全屏,只影响展示。链接多一个参数,不应改变身份与资源授权。
退出后怎样确认真的离开
产品需要约定退出的是当前应用会话,还是统一会话。只删本地会话,统一认证仍有效时,再访问应用可能自动登录;这应是有意定义的行为,而不是让用户猜退出按钮有没有生效。
验证可以固定一条设备链接,覆盖会话过期、重新认证、组织切换、跨标签退出和重新访问。A 的响应不能落到 B 的页面,退出后的请求按会话规则失效,重新登录仍能恢复合法目标。访问路径完整通过,SSO 才能在用户日常工作中用起来。