如何实现用户登录状态的即刻退出

160 阅读4分钟

image.png

场景:由于oauth2单点登录的认证和二级跨域问题,登录页面部署在后端中,前端框架vben-admin。

需求:用户若选择记住我的登录状态,则在token和refresh_token不过期的时间段再次访问可以直接访问,无需再次登录;用户若选择不记住,则在本次窗口关闭之后,下一次访问需要再次重新登录。

下面是请教同事以及百度的一些方法,但都有一些缺陷:

一、使用beforeunload、定时器、worker线程

image.png

暂时只能找到这个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);
}

存在问题:

  1. 30s 只是预估的,遇上网速慢、内存少,无法正常运行
  2. 定时器一直在运行
  3. 存在缓存和一些特殊情况,容易出现二次登录问题

二、选择不同的存储方式 将不记住登录状态的用户的信息保存在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也可以拿到最新的值,但是又又存在一个问题。。。

image.png token和refresh_token都被保存在了localStorage当中,更新permissionCacheType晚于存储token和刷新refresh_token

如果登录页面是部署在前端,这个问题应该比较好解决了,在勾选状态的时候就设置permissionCacheType的类型

有没有dl做过类似的需求,或有什么思路提供一下,感谢感谢