场景:由于oauth2单点登录的认证和二级跨域问题,登录页面部署在后端中,前端框架vben-admin。
需求:用户若选择记住我的登录状态,则在token和refresh_token不过期的时间段再次访问可以直接访问,无需再次登录;用户若选择不记住,则在本次窗口关闭之后,下一次访问需要再次重新登录。
下面是请教同事以及百度的一些方法,但都有一些缺陷:
一、使用beforeunload、定时器、worker线程
暂时只能找到这个api与窗口的关闭有关。由于刷新和窗口的关闭都会进行一次页面的卸载,自然会执行一次beforeunload事件,所以只能通过预估时间与判断窗口是关闭还是刷新。
当用户登录成功之后并且选择不记住状态时,后台会开启一个worker,并将此时的时间戳记录在本地存储(timeStamp)当中,每隔1s刷新本地存储的事件戳。所以只要这个页面一直在浏览器中,则这个定时器一直在运行。。。
当页面卸载时,本地存储只能记录到卸载时刻的时间戳,所以下一次访问网站时(在token未失效),判断本次本地存储的时间戳与此刻的时间戳,如果差值大于30s,则认为是重新访问,此时再调用后端的退出接口,并清空缓存;如果差值小于30s,则认为是刷新,正常访问。
/*App.vue*/
const userStore = useUserStore();
const workerStore = useWorkerStore();
const getUserInfo = computed(() => {
return userStore.getUserInfo;
});
watch(
() => getUserInfo.value,
(value) => {
if (value?.data.login_status === 0) {
// 不记住我的登录状态 执行
doSomething();
}
},
);
async function doSomething() {
workerStore.setWorker();
const unloadTimestamp = localStorage.getItem('unloadTimestamp');
if (unloadTimestamp) {
const differTime = Date.now() - Number(unloadTimestamp);
if (differTime > 30 * 1000) {
// 大于30s则退出,时间不好把握
workerStore.clearWorker();
await userStore.logout();
}
}
}
/*worker.js*/
let timer;
const unloadTimestamp = () => {
timer = setInterval(() => {
postMessage(Date.now());
}, 1000);
};
unloadTimestamp();
addEventListener('message', (e) => {
if (e.data === 'clearInterval') {
clearInterval(timer);
}
});
/*workerStore.js*/
import { defineStore } from 'pinia';
import { store } from '/@/store';
interface WorkerState {
worker: Worker | null;
}
export const useWorkerStore = defineStore({
id: 'app-worker',
state: (): WorkerState => ({
worker: null,
}),
actions: {
setWorker() {
this.worker = new Worker(new URL('/@/worker.js', import.meta.url));
this.worker.addEventListener('message', (e: any) => {
localStorage.setItem('unloadTimestamp', e.data);
});
},
clearWorker() {
this.worker?.postMessage('clearInterval');
this.worker?.terminate();
},
},
});
// Need to be used outside the setup
export function useWorkerStoreWithout() {
return useWorkerStore(store);
}
存在问题:
- 30s 只是预估的,遇上网速慢、内存少,无法正常运行
- 定时器一直在运行
- 存在缓存和一些特殊情况,容易出现二次登录问题
二、选择不同的存储方式 将不记住登录状态的用户的信息保存在sessionStorage中,记住登录状态的用户信息保存在localStorage中
处理sessionStorage的跨标签页问题 学习文章:sessionStorage不能跨标签页解决方案--vue项目_sessionstorage跨页面-CSDN博客
/*main.js*/
;(function () {
console.log('自执行了')
if (!sessionStorage.length) {
localStorage.setItem('getSessionStorage', Date.now() as unknown as string)
}
window.addEventListener('storage', function (event) {
console.log(event, 'event')
if (event.key === 'getSessionStorage') {
console.log('sessionStorage', JSON.stringify(sessionStorage))
localStorage.setItem('sessionStorage', JSON.stringify(sessionStorage))
this.localStorage.removeItem('sessionStorage')
} else if (event.key === 'sessionStorage' && !sessionStorage.length) {
const data = JSON.parse(event.newValue ?? '{}')
for (const key in data) {
sessionStorage.setItem(key, data[key])
}
}
})
})()
此处是可以进行sessionStorage的跨标签页共享的,但如果在各自页面修改sessionStorage的数据,其他页签不能同步,只有关闭之后重新打开才能同步,不过此处应该可以用beforeunload清空掉sessionStorage,刷新之后之后可拿到最新的sessionStorage中的内容?(还未试过
问题到这基本解决,但由于到vben-admin封装的强大性,无法及时动态更新projectSetting里面的缓存类型
项目挂载后,先导入auth.ts文件,所以在auth.ts文件拿到的permissionCacheType一开始就是一个默认值,LocalStorage值
登录成功之后需要使用auth.ts存储数据的方法
/*projectSetting.ts*/
const setting: ProjectConfig = {
permissionCacheType:'LocalStorage'
}
/*auth.ts*/
import projectSetting from '/@/settings/projectSetting';
const { permissionCacheType } = projectSetting;
let isLocal = permissionCacheType === CacheTypeEnum.LOCAL;
export function getAuthCache<T>(key: BasicKeys) {
const fn = isLocal ? Persistent.getLocal : Persistent.getSession;
return fn(key) as T;
}
export function setAuthCache(key: BasicKeys, value) {
const fn = isLocal ? Persistent.setLocal : Persistent.setSession;
return fn(key, value, true);
}
因此需要通知到auth.ts文件projectting里面的permissionCacheType已经发生更改,想到的就是再开一个共享线程来进行vue文件和ts文件的通信。
if (res.data.login_status == 0) {
if (!window.SharedWorker) {
throw new Error('当前浏览器不支持SharedWorker,无法启动后台服务');
}
const worker = new SharedWorker('shared-worker.js');
worker.port.start();
worker.port.postMessage({
permissionCacheType: CacheTypeEnum.SESSION,
});
worker.port.addEventListener('message', (e) => {
console.log('我收到啦--code', e);
});
}
if (res.status == 200) {
userStore.setRefreshToken(res.data.refresh_token);
await userStore.login(res.data.access_token, route.query);
}
/*auth.ts 补充线程*/
let isLocal = permissionCacheType === CacheTypeEnum.LOCAL;
const worker = new SharedWorker('shared-worker.js');
worker.port.start();
worker.port.postMessage({
permissionCacheType: CacheTypeEnum.LOCAL,
});
worker.port.addEventListener('message', (e) => {
isLocal = e.data.permissionCacheType === CacheTypeEnum.LOCAL;
});
此时可以正常通信,并且isLocal也可以拿到最新的值,但是又又存在一个问题。。。
token和refresh_token都被保存在了localStorage当中,更新permissionCacheType晚于存储token和刷新refresh_token
如果登录页面是部署在前端,这个问题应该比较好解决了,在勾选状态的时候就设置permissionCacheType的类型
有没有dl做过类似的需求,或有什么思路提供一下,感谢感谢