后台登录方案(包含环境配置、请求封装、登录鉴权)

登录方案分析:

  1. 封装 axios 模块 (环境变量配置)

  2. 封装 接口请求 模块

  3. 封装登录请求动作

  4. 保存服务端返回的 token

  5. 登录鉴权 (白名单、判断token、退出登录方案)

封装 axios 模块

1. 创建axios实例:定义请求baseURL、请求超时时间

2.请求、响应拦截

import store from '@/store'
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { isCheckTimeout } from '@/utils/auth'
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 6000
})

// 请求拦截
service.interceptors.request.use(
  (config) => {
    if (store.getters.token) {
      // 判断前端定时的token是否超时
       if (!isCheckTimeout) {
        store.dispatch('user/logout')
        return Promise.reject(new Error('token 失效'))
      }
       存在token 注入请求头
       config.headers.Authorization = `Bearer ${store.getters.token}`
    }
    // 配置接口国际化
    config.headers['Accept-Language'] = store.getters.language
    return config
  },
  (error) => {
    // 判断服务器的token是否超时,超时返回code=401
    if (
      error.response &&
      error.response.data &&
      error.response.data.code === 401
    ) {
      store.dispatch('user/logout')
    }
    ElMessage.error(error.message) // 提示错误信息
    return Promise.reject(error)
  }
)

// 响应拦截
service.interceptors.response.use(
  (response) => {
    const { success, message, data } = response.data
    if (success) {
      return data
    } else {
      ElMessage.error(message)
      return Promise.reject(new Error(message))
    }
  },
  (error) => {
    ElMessage.error(error.message)
    return Promise.reject(error)
  }
)

export default service

baseURL 获取的环境变量

创建两个变量 .env.development 、.env.production 

ENV = 'production'
VUE_APP_BASE_API = '/prod-api'

================================

ENV = 'development'
VUE_APP_BASE_API = '/api'

axios模块中获取环境变量 

process.env.VUE_APP_BASE_API

封装请求动作

创建 api 文件夹,创建 sys.js

import request from '@/utils/request'

/**
 * 登录
 */
export const login = data => {
  return request({
    url: '/sys/login',
    method: 'POST',
    data
  })
}

封装登录请求动作:

该动作我们期望把它封装到 vuexaction

store 下创建 modules 文件夹,创建 user.js 模块,用于处理所有和 用户相关 的内容(此处需要使用第三方包 md5

import { login } from '@/api/sys'
import md5 from 'md5'
export default {
  namespaced: true,
  state: () => ({}),
  mutations: {},
  actions: {
    login(context, userInfo) {
      const { username, password } = userInfo
      return new Promise((resolve, reject) => {
        login({
          username,
          password: md5(password)
        })
          .then(data => {
            resolve()
          })
          .catch(err => {
            reject(err)
          })
      })
    }
  }
}

store/index 中完成注册

import { createStore } from 'vuex'
import user from './modules/user.js'
export default createStore({
  modules: {
    user
  }
})

登录触发动作

login 中,触发定义的 action

<template>
	<el-button
        type="primary"
        style="width: 100%; margin-bottom: 30px"
        :loading="loading"
        @click="handleLogin"
        >登录</el-button
      >
</template>
<script setup>
import { ref } from 'vue'
import { validatePassword } from './rules'
import { useStore } from 'vuex'
...

// 登录动作处理
const loading = ref(false)
const loginFromRef = ref(null)
const store = useStore()
const handleLogin = () => {
  loginFromRef.value.validate(valid => {
    if (!valid) return

    loading.value = true
    store
      .dispatch('user/login', loginForm.value)
      .then(() => {
        loading.value = false
        // TODO: 登录后操作
      })
      .catch(err => {
        console.log(err)
        loading.value = false
      })
  })
}
</script>

前面配置环境变量时指定了 开发环境下,请求的 BaseUrl/api ,所以我们真实发出的请求为:/api/sys/login

这样的一个请求会被自动键入到当前前端所在的服务中,所以我们最终就得到了 http://192.168.18.42:8081/api/sys/login 这样的一个请求路径。

而想要处理这个问题,那么可以通过指定 webpack DevServer 代理 的形式,代理当前的 url 请求。

而指定这个代理非常简单,是一种近乎固定的配置方案。

vue.config.js 中,加入以下代码:

module.exports = {
  devServer: {
    // 配置反向代理
    proxy: {
      // 当地址中有/api的时候会触发代理机制
      '/api': {
        // 要代理的服务器地址  这里不用写 api
        target: 'https://api.imooc-admin.lgdsunday.club/',
        changeOrigin: true // 是否跨域
      }
    }
  },
  ...
}

保存服务端返回的 token

1. 在 vuexuser 模块下,处理 token 的保存

import { login } from '@/api/sys'
import md5 from 'md5'
import { setItem, getItem } from '@/utils/storage'
import { TOKEN } from '@/constant'
export default {
  namespaced: true,
  state: () => ({
    token: getItem(TOKEN) || ''
  }),
  mutations: {
    setToken(state, token) {
      state.token = token
      setItem(TOKEN, token)
    }
  },
  actions: {
    login(context, userInfo) {
      ...
          .then(data => {
            // 后续响应拦截配置返回数据简化操作后
            this.commit('user/setToken', data.token)
            resolve()
          })
          ...
      })
    }
  }
}

2. 响应数据统一处理 (获取token数据简化操作,请求、响应拦截器配置)

utils/request.js 中实现以下代码

import axios from 'axios'
import { ElMessage } from 'element-plus'

...
// 响应拦截器
service.interceptors.response.use(
  response => {
    const { success, message, data } = response.data
    //   要根据success的成功与否决定下面的操作
    if (success) {
      return data
    } else {
      // 业务错误
      ElMessage.error(message) // 提示错误消息
      return Promise.reject(new Error(message))
    }
  },
  error => {
    // TODO: 将来处理 token 超时问题
    ElMessage.error(error.message) // 提示错误信息
    return Promise.reject(error)
  }
)

export default service

登录鉴权

 在 main.js 平级,创建 permission 文件

import router from './router'
import store from './store'

// 白名单
const whiteList = ['/login']
/**
 * 路由前置守卫
 */
router.beforeEach(async (to, from, next) => {
  // 存在 token ,进入主页
  // if (store.state.user.token) {
  // 快捷访问
  if (store.getters.token) {
    if (to.path === '/login') {
      next('/')
    } else {
      next()
    }
  } else {
    // 没有token的情况下,可以进入白名单
    if (whiteList.indexOf(to.path) > -1) {
      next()
    } else {
      next('/login')
    }
  }
})

在此处我们使用到了 vuex 中的 getters ,此时的 getters 被当作 快捷访问 的形式进行访问

所以我们需要声明对应的模块,创建 store/getters

const getters = {
  token: state => state.user.token
}
export default getters

 在 store/index 中进行导入:

import getters from './getters'
export default createStore({
  getters,
  ...
})

退出登录方案 

  1. 用户主动退出

    点击退出,清空token
  2. 用户被动退出

token 过期或被 其他人”顶下来“ 时退出

        1. 主动方案
        2. 被动方案

核心:

  1. 清理掉当前用户缓存数据
  2. 清理掉权限相关配置
  3. 返回到登录页

被动退出的主动方案之前端主动介入 token 时效的处理

  1. 在用户登陆时,记录当前 登录时间
  2. 制定一个 失效时长
  3. 在接口调用时,根据 当前时间 对比 登录时间 ,看是否超过了 时效时长
    1. 如果未超过,则正常进行后续操作
    2. 如果超过,则进行 退出登录 操作

创建 utils/auth.js 文件,并写入以下代码

import { TIME_STAMP, TOKEN_TIMEOUT_VALUE } from '@/constant'
import { setItem, getItem } from '@/utils/storage'
/**
 * 获取时间戳
 */
export function getTimeStamp() {
  return getItem(TIME_STAMP)
}
/**
 * 设置时间戳
 */
export function setTimeStamp() {
  setItem(TIME_STAMP, Date.now())
}
/**
 * 是否超时
 */
export function isCheckTimeout() {
  // 当前时间戳
  var currentTime = Date.now()
  // 缓存时间戳
  var timeStamp = getTimeStamp()
  return currentTime - timeStamp > TOKEN_TIMEOUT_VALUE
}

constant 中声明对应常量  

// token 时间戳
export const TIME_STAMP = 'timeStamp'
// 超时时长(毫秒) 两小时
export const TOKEN_TIMEOUT_VALUE = 2 * 3600 * 1000

在用户登录成功之后去设置时间,到 store/user.jslogin

import { setTimeStamp } from '@/utils/auth'

login(context, userInfo) {
      ...
      return new Promise((resolve, reject) => {
        ...
          .then(data => {
            ...
            // 保存登录时间
            setTimeStamp()
            resolve()
          })
      })
    },

utils/request 对应的请求拦截器中进行 主动介入

import { isCheckTimeout } from '@/utils/auth'

if (store.getters.token) {
      if (isCheckTimeout()) {
        // 登出操作
        store.dispatch('user/logout')
        return Promise.reject(new Error('token 失效'))
      }
      ...
    }

用户被动退出解决方案之被动处理

  1. token 过期
    服务器返回的token过期
  2. 单用户登录
    服务器返回状态
核心:在请求时,服务器返回的状态码辨认

utils/request 的响应拦截器中,增加以下逻辑

// 响应拦截器
service.interceptors.response.use(
  response => {
    ...
  },
  error => {
    // 处理 token 超时问题
    if (
      error.response &&
      error.response.data &&
      error.response.data.code === 401
    ) {
      // token超时
      store.dispatch('user/logout')
    }
    ElMessage.error(error.message) // 提示错误信息
    return Promise.reject(error)
  }
)

  • 44
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值