今天分享一个常见的场景:多页面应用怎么复用登录状态?比如微前端架构下,多个子应用都需要登录,但每个都搞个登录页太麻烦,用户体验也不连贯。那我们能不能做一个统一的登录页,登录一次,所有页面都同步登录状态呢?当然可以!下面就来分享我们的方案。
注意:这个方案不太适用移动端,但使用Cookie来实现登录状态同步的思路是一致的
思路很简单
我们打算这样干:
- 在任何需要登录的页面,点击登录按钮,弹出一个新窗口打开统一的登录页。
- 用户在登录页完成登录,后端设置Cookie(确保域名相同,路径为根路径)。
- 登录成功后,登录页通过
window.opener通知父窗口,然后关闭自己。 - 父窗口检测到登录完成,执行回调函数,更新本地登录状态。
这样,同一个域名下的所有页面都能共享Cookie中的登录状态,实现一次登录,处处通行。
代码实现
父页面逻辑
我们封装一个useLogin钩子,主要做两件事:打开登录窗口和监听登录结果。
javascript
import { isFunction } from "@/utils/common"
export function useLogin() {
let loginPage = null
const login = (options = {}, cb = null) => {
const url = 'pages/login/index'
// 新窗口打开登录页,设置好窗口参数
loginPage = window.open(url, '_blank', 'width=375,height=640,status=0,titlebar=0,toolbar=0,resizable=0,menubar=0,location=0,directories=0,left=100,top=100')
loginPage.focus()
// 避免有些浏览器焦点没抢到,再试一次
setTimeout(() => loginPage.focus(), 10)
// 监听焦点事件,判断登录窗口是否关闭
window.onfocus = async function(activeCall) {
setTimeout(() => {
// 如果登录窗口关闭或者登录页主动通知(activeCall为"1")
if (loginPage?.closed || activeCall == "1") {
loginPage = null
window.onfocus = null
// 执行回调
if (cb) {
if (isFunction(cb)) {
cb(activeCall)
} else {
console.error('回调函数不是Function类型')
}
}
}
}, 100)
}
}
return {
login
}
}
登录页逻辑
登录页在登录接口调用成功后,通知父窗口,然后关闭自己。
javascript
// 登录接口调用成功后
try {
// 通知父窗口,传递参数"1"表示登录成功
await window.opener.onfocus("1")
} catch(e) {
// 如果通知失败,比如父窗口已经关闭,我们打印错误
console.log(e)
}
// 关闭登录窗口
window.close()
关键点说明
1. 窗口通信
我们用了window.opener来获取打开当前窗口的父窗口,然后调用父窗口的onfocus方法。这种方法简单直接,但要注意跨域限制,所以我们的登录页和父页面必须是同域名的。
2. 回调函数处理
父窗口通过回调函数来响应登录结果,例如更新对应的用户数据。我们做了类型检查,确保回调是函数才执行。
3. Cookie设置
登录成功后,后端设置Cookie时,要设置path=/和domain为主域名,这样所有子页面都能读到。
遇到的问题和解决方案
浏览器拦截弹窗?
有的浏览器可能会拦截window.open,所以我们最好在用户点击事件中触发登录窗口打开,避免异步打开。
移动端怎么办?
移动端不建议用新窗口,可以直接跳转到登录页,登录成功后跳转回来。我们可以根据设备类型选择不同的策略。
总结
这个方案已经在我们项目中稳定运行,用户体验良好。代码量不多,但实现了多页面登录状态共享。当然,还有优化空间,比如加入登录状态过期自动跳转等。