怎么实现一个登录页面#鉴权#导航守卫#token

怎么实现一个登录页面

  1. 在api目录下面,建立一个login.js文件, 配置三个发送axios请求的函数并导出

    1. 获取token
    2. 使用token兑换info
    3. 退出登录
    // login.js
    // 获取axios实例request
    import request from '@/util/request';
    
    // 登录,获取token
    const reqLogin = (username, password) =>
      request.post('/admin/login', { username, password });
    
    // 使用token兑换info
    const reqGetInfo = () => request.post('/admin/getinfo');
    
    // 退出登录
    const reqLogout = () => request.post('/admin/logout');
    
    // 导出
    export { reqLogin, reqGetInfo, reqLogout };
    
  2. 在utils目录里面, 创建一个user.js文件夹, 用来存放一些token工具

    1. 添加token
    2. 获取token值
    3. 移除token
    // user.js
    // 添加token
    export function setToken(token) {
      localStorage.setItem('shopAdmin-token', token);
    }
    
    //获取token
    export function getToken() {
      return localStorage.getItem('shopAdmin-token');
    }
    
    // 移除token
    export function removeToken() {
      return localStorage.removeItem('shopAdmin-token');
    }
    
    
  3. 在stores目录下面, 新建一个pinia仓库loginStore.js , 用做登录数据的存储库

    1. state: ①token ②info
    2. actions: ①获取token ②token兑换info ③登出并删除token和info
    // loginStore.js
    import { defineStore } from 'pinia';
    import { setToken, getToken, removeToken } from '@/util/user.js';
    import { reqLogin, reqGetInfo, reqLogout } from '@/api/login.js';
    
    export const useLoginStore = defineStore('login', {
      state: () => {
        return {
          token: getToken() || '',
          info: '',
        };
      },
      actions: {
    
        // 1. 获取token
        async getAndSaveToken({ username, password }) {
          try {
            const { token } = await reqLogin(username, password);
            console.log(token);
            //  存一份到localStorege
            setToken(token);
            //  本仓库存一份
            this.token = token;
          } catch (error) {
            // console.log(error);
            return Promise.reject(error);
          }
        },
    
        //  2.使用token兑换info
        async getInfoByToken() {
          try {
            const info = await reqGetInfo();
            this.info = info || {};
          } catch (error) {
            // console.log(error);
            return Promise.reject(error);
          }
    
        // 3.派发退出登录actions
        logout() {
          // 不在意请求的响应结果
          reqLogout();
          // 清空本仓库的token和info
          this.token = '';
          this.info = {};
          // 清空localStorege
          removeToken();
        },
      },
    });
    
    

    ? 为什么前两个actions都要tryCatch一下, 并返回一个错误的promise?

    这是为了别的页面调用这个actions也能tryCatch处理错误

  4. 在pages目录下面的单文件组件中, 比如login.vue, 在表单验证成功之后, 处理以下逻辑

    1. 表单验证通过
    2. 获取并存储token
    3. 使用token兑换info
    4. 去往首页
    // login.vue
    // 引入登录页的store和vue-router
    import { useLoginStore } from '@/stores/loginStore.js';
    import { useRouter } from 'vue-router';
    // 创建两个对象
    const router = useRouter();
    const loginStore = useLoginStore();
    
    
    ......
    假设上面已经表单校验通过
    
    // 在表单验证成功之后: 
    // 1. 获取并存储token
        try {
          await loginStore.getAndSaveToken({
            username: form.username,
            password: form.password,
          });
        } catch (err) {
          alert('用户名或密码错误');
          return;
        }
    
        //  2. 使用token兑换info
        try {
          await loginStore.getInfoByToken();
        } catch (error) {
          alert('获取用户信息失败');
          return;
        }
    
        // 3. 去往首页
        router.push('/');
    
    
  5. 将token通过请求拦截器, 添加在请求头里面

    1. 引入loginStore

    2. 通过loginStore获取token

    3. 将token添加在请求头中

      注意 :尽量从pinia仓库获取token和info数据, 从localStorege获取性能很差

    import { useLoginStore } from '@/stores/loginStore.js';
    
    ......
    
    request.interceptors.request.use((config) => {
      const token = useLoginStore().token;
      if (token) {
        // 将token设置在请求头中
        config.headers.token = token;
      }
      return config;
    });
    
    ......
    
  6. 配置全局前置守卫

    1. 定义白名单
    2. 从loginStore里面获取token和info
    3. 根据不同情况, 定义不同规则
    // 定义白名单页面数组
    const whiteList = ['/login'];
    
    router.beforeEach(async (to, from, next) => {
      // 引入登录的store
      const loginStore = useLoginStore();
      // 得到token和info里面的username
      const token = loginStore.token;
      const username = loginStore.info.username;
      // 如果没有token
      if (!token) {
        // 是白名单的页面
        if (whiteList.includes(to.path)) {
          // 放行
          next();
        } else {
          // 不是白名单页面, 直接回到登录页
          alert('请登录!');
          next('/login');
        }
      } else {
        // 如果有token, 想从别的页面去login
        if (to.path == '/login') {
          // 不让去登录页
          alert('不可直接去登录页,请先退出登录');
          next(false);
        } else {
          // 如果去的不是登录页
          // 如果有info信息
          if (username) {
            next();
          } else {
            try {
              // 如果没有info信息, 派发actions让仓库重新获取info
              await loginStore.getInfoByToken();
              next();
            } catch (error) {
              // 获取失败,视为token过期
              // 派发退出登录action
              loginStore.logout();
              alert('登录状态过期,请重新登录');
              next('/login');
            }
          }
        }
      }
    });
    

上述的路由规则逻辑如下

posted @ 2023-08-12 09:40  dimiaslong  阅读(27)  评论(0)    收藏  举报