登录回跳里的 callback 参数为什么会丢失

前几天排查登录页面时碰到一个问题:未登录访问某个页面,跳到登录页,登录成功后应该回到原来的地址。但原地址里的查询参数没能完整带回来。

参数怎么丢的

当时的目标地址类似这样:

1
/view/dashboard/todo?key=order&proj_key=jd

如果直接把它拼到 callback 后面:

1
/view/auth/login?callback=/view/dashboard/todo?key=order&proj_key=jd

问题就出在这个 &。浏览器解析外层 URL 时,会把 proj_key=jd 当成登录页的另一个参数,而不是 callback 的一部分。结果读取 callback 时,只拿到了:

1
/view/dashboard/todo?key=order

后来回头看,其实就是把一个带参数的 URL 原样塞进了另一个 URL。这里要先把整个目标地址编码,再作为参数放进去:

1
const loginUrl = `/view/auth/login?callback=${encodeURIComponent(target)}`

登录页读取时可以用 URLSearchParams,不用自己去找 ?callback= 的位置,再用 substr 截字符串:

1
2
3
4
5
const fallback = '/view/project-list'
const callback = new URLSearchParams(window.location.search).get('callback')
const target = callback?.startsWith('/view/') ? callback : fallback

window.location.assign(target)

URLSearchParams 读取时已经会对参数值解码,不需要再无条件调用一次 decodeURIComponent。另外,回跳地址也不能拿到什么就跳什么。这个例子只允许 /view/ 下的路径,其他情况回到默认页;实际项目还要按自己的路由规则做校验。

登录页反复跳转

排查过程中还遇到了另一个问题:鉴权中间件放行的是 /view/auth,但实际访问的登录页是 /view/auth/login。

未登录时访问登录页,中间件发现没有登录,又把请求重定向回登录页,结果就开始循环,URL 里的 callback 也越套越长。把实际登录页路径加入白名单后,才停下来。登录接口也要确认在放行范围内,其他受保护页面则继续走鉴权。

之后再检查这个功能,我会用带多个参数的地址试一遍,也会试试参数里本身有编码字符的情况。同时确认直接打开登录页不会循环,乱填或填写外部回跳地址时会回到默认页。