大事件项目拆解:登录访问拦截实现详解

一、项目概述

大事件项目是一个典型的后台管理系统,包含用户登录、权限管理、内容发布等功能模块。其中登录访问拦截是系统的核心安全机制之一,确保只有认证用户才能访问特定资源。

二、技术栈

  • 前端:Vue.js + Element UI

  • 后端:Node.js + Express/Koa

  • 状态管理:Vuex/Pinia

  • 路由:Vue Router

  • 存储:localStorage/sessionStorage

三、登录拦

1. 路由拦截实现

路由配置
javascript 复制代码
//src>router>index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores'
// createRouter用于创建路由实例,
// 配置history模式
// 1. history模式 createWebHistory 地址栏不带警号
// 2.带井号

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    { path: '/login', component: () => import('@/views/login/loginPage.vue') },
    {
      path: '/',
      component: () => import('@/views/layout/LayoutContainer.vue'),
      redirect: '/article/manage',
      children: [
        {
          path: '/article/manage',
          component: () => import('@/views/artical/ArticleManage.vue')
        },
        {
          path: '/article/channel',
          component: () => import('@/views/artical/ArticleChannel.vue')
        },
        {
          path: '/user/profile',
          component: () => import('@/views/user/UserProfile.vue')
        },
        {
          path: '/user/avatar',
          component: () => import('@/views/user/UserAvatar.vue')
        },
        {
          path: '/user/password',
          component: () => import('@/views/user/UserPassword.vue')
        }
      ]
    }
  ]
路由守卫实现
javascript 复制代码
//src>router>index.js
router.beforeEach((to) => {
  // 会在每次路由跳转之前执行,用来拦截和验证用户是否有权限访问某个页面。
  const useStore = useUserStore()
  // 从 Pinia store 中获取用户状态信息
  if (!useStore.token && to.path !== '/login') return '/login'
  // useStore.token 获取用户的登录令牌

  return true
})

2. 登录状态管理

Vuex状态管理
javascript 复制代码
//src>stores>models>user.js
import { userGetInfoService } from '@/api/user'
import { defineStore } from 'pinia'
import { ref } from 'vue'

// 用户模块 token setToken removeToken
export const useUserStore = defineStore(
  'big-user',
  () => {
    // 组合式API写法
    const token = ref('')
    // token: 存储用户认证令牌的响应式引用
    const setToken = (newToken) => {
      // setToken(newToken): 设置新的令牌
      token.value = newToken
    }
    const removeToken = () => {
      // removeToken(): 清除令牌
      token.value = ''
    }
    const user = ref({})
    // user: 存储用户信息的响应式引用,初始值为空对象
    const getUser = async () => {
      const res = await userGetInfoService()
      // getUser(): 异步函数,调用 API 获取用户信息并更新 user 状态
      user.value = res.data.data
    }

    return {
      token,
      setToken,
      removeToken,
      user,
      getUser
    }
  },
  {
    persist: true
    // persist: true 表示这个状态存储会被持久化保存,即使页面刷新也能保持状态
  }
)

3. 登录组件实现

javascript 复制代码
//src>views>login>loginPage.vue
<script setup>
import { userRegisterService, userLoginService } from '@/api/user.js'
// import router from '@/router'
import { useUserStore } from '@/stores'
import { User, Lock } from '@element-plus/icons-vue'
import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'

const isRegister = ref(false)
const form = ref()
// 整个的用于提交的数据对象
const formModel = ref({
  username: '',
  password: '',
  repassword: ''
})
// 整个表单的校验规则
const rules = {
  username: [
    { required: true, message: 'Please input Activity name', trigger: 'blur' },
    { min: 5, max: 10, message: '用户名', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    {
      pattern: /^\S{6,15}$/,
      message: '密码必须是6-15位的非空字符',
      trigger: 'blur'
    }
  ],
  repassword: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    {
      // 自定义校验
      validator: (rule, value, callback) => {
        if (value !== formModel.value.password) {
          callback(new Error('两次输入密码不一致'))
        } else {
          callback() //就算成立了也需要callback
        }
      },
      trigger: 'blur'
    }
  ]
}
const register = async () => {
  await form.value.validate()
  await userRegisterService(formModel.value)
  ElMessage.success('Congrats, this is a success message.')
  // isRegister.value = false
}

// 调用方法将 token 存入 pinia 并 自动持久化本地
const userStore = useUserStore()
const router = useRouter()
const login = async () => {
  await form.value.validate()
  const res = await userLoginService(formModel.value)
  userStore.setToken(res.data.token)
  ElMessage.success('登录成功')
  router.push('/')
  console.log('开始登录', res)
}

//切换的时候,重置表单内容
watch(isRegister, () => {
  formModel.value = {
    username: '',
    password: '',
    repassword: ''
  }
})
</script>

<template>
  <el-row class="login-page">
    <el-col :span="12" class="bg"></el-col>
    <el-col :span="6" :offset="3" class="form">
      <!-- 注册相关表单 -->
      <el-form
        :model="formModel"
        :rules="rules"
        ref="form"
        size="large"
        autocomplete="off"
        v-if="isRegister"
      >
        <el-form-item>
          <h1>注册</h1>
        </el-form-item>
        <el-form-item prop="username">
          <el-input
            v-model="formModel.username"
            :prefix-icon="User"
            placeholder="请输入用户名"
          ></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input
            v-model="formModel.password"
            :prefix-icon="Lock"
            type="password"
            placeholder="请输入密码"
          ></el-input>
        </el-form-item>
        <el-form-item prop="repassword">
          <el-input
            v-model="formModel.repassword"
            :prefix-icon="Lock"
            type="password"
            placeholder="请再次输入密码"
          ></el-input>
        </el-form-item>
        <el-form-item>
          <el-button
            @click="register"
            class="button"
            type="primary"
            auto-insert-space
          >
            注册
          </el-button>
        </el-form-item>
        <el-form-item class="flex">
          <el-link type="info" :underline="false" @click="isRegister = false">
            ← 返回
          </el-link>
        </el-form-item>
      </el-form>
      <!-- 登录相关表单 -->
      <el-form
        :model="formModel"
        :rules="rules"
        ref="form"
        size="large"
        autocomplete="off"
        v-else
      >
        <el-form-item>
          <h1>登录</h1>
        </el-form-item>
        <el-form-item prop="username">
          <el-input
            v-model="formModel.username"
            name="username"
            :prefix-icon="User"
            placeholder="请输入用户名"
          ></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input
            v-model="formModel.password"
            name="password"
            :prefix-icon="Lock"
            type="password"
            placeholder="请输入密码"
          ></el-input>
        </el-form-item>

        <el-form-item class="flex">
          <div class="flex">
            <el-checkbox>记住我</el-checkbox>
            <el-link type="primary" :underline="false">忘记密码?</el-link>
          </div>
        </el-form-item>
        <el-form-item>
          <el-button
            @click="login"
            class="button"
            type="primary"
            auto-insert-space
            >登录</el-button
          >
        </el-form-item>
        <el-form-item class="flex">
          <el-link type="info" :underline="false" @click="isRegister = true">
            注册 →
          </el-link>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<style lang="scss" scoped>
.login-page {
  height: 100vh;
  background-color: #fff;
  .bg {
    background:
      url('@/assets/logo2.png') no-repeat 60% center / 240px auto,
      url('@/assets/login_bg.jpg') no-repeat center / cover;
    border-radius: 0 20px 20px 0;
  }
  .form {
    display: flex;
    flex-direction: column;
    justify-content: center;
    user-select: none;
    .title {
      margin: 0 auto;
    }
    .button {
      width: 100%;
    }
    .flex {
      width: 100%;
      display: flex;
      justify-content: space-between;
    }
  }
}
</style>

实现页面:

4. 请求拦截器实现

javascript 复制代码
//src>utils>request
import axios from 'axios'
import { useUserStore } from '@/stores'
import { ElMessage } from 'element-plus'
import router from '@/router'
const baseURL = 'http://big-event-vue-api-t.itheima.net'

const instance = axios.create({
  // TODO 1. 基础地址,超时时间
  baseURL,
  timeout: 10000
})

//请求拦截器
instance.interceptors.request.use(
  (config) => {
    // TODO 2. 携带token
    const useStore = useUserStore()
    if (useStore.token) {
      config.headers.Authorization = useStore.token
    }
    return config
  },
  (err) => Promise.reject(err)
)

//响应拦截器
instance.interceptors.response.use(
  (res) => {
    // TODO 3. 处理业务失败
    // TODO 4. 摘取核心响应数据
    if (res.data.code === 0) {
      return res
    }
    //处理业务失败,给错误提示,抛出错误
    ElMessage.error(res.data.message || '服务异常')
    return Promise.reject(res.data)
  },
  (err) => {
    ElMessage.error(err.response.data.message || '服务异常')
    // TODO 5. 处理401错误
    if (err.response?.status === 401) {
      router.push('/login')
    }

    //错误的默认qingkuang
    ElMessage.error(err.response.data.message)
    return Promise.reject(err)
  }
)

export default instance
export { baseURL }

五、安全最佳实践

  1. 密码安全

    • 前端应对密码进行基本校验(长度、复杂度)

    • 使用HTTPS传输敏感数据

  2. 日志与监控

    • 记录登录失败事件

    • 监控异常登录行为

六、常见问题与解决方案

  1. 页面刷新后Vuex状态丢失

    • 解决方案:结合localStorage实现状态持久化
  2. 多标签页登录状态同步

    • 解决方案:使用storage事件监听localStorage变化
  3. 权限动态加载

    • 解决方案:实现后端返回权限路由表,前端动态添加路由

七、总结

登录访问拦截是Web应用安全的第一道防线,本文详细介绍了从路由拦截、状态管理到请求拦截的完整实现方案。实际项目中应根据具体需求和安全要求进行调整,同时结合后端的安全措施共同构建完善的认证授权体系。

相关推荐
Dragon Wu12 小时前
TailWindCss 核心功能总结
前端·css·前端框架·postcss
Dragon Wu15 小时前
TanStack Query(React Query) 常用api及操作总结
前端·javascript·前端框架
前端达人15 小时前
原生JavaScript vs 前端框架,2026年该怎么选?
开发语言·前端·javascript·前端框架·ecmascript
漫天黄叶远飞15 小时前
React 组件通讯全攻略:拒绝 "Props" 焦虑,掌握数据流动的艺术
前端·react.js·前端框架
文心快码BaiduComate16 小时前
插件开发实录:我用Comate在VS Code里造了一场“能被代码融化”的初雪
前端·后端·前端框架
running up1 天前
Pinia 完整使用指南
vue
安_2 天前
<style scoped>跟<style>有什么区别
前端·vue
道法自然|~2 天前
【建站】网站使用天地图
html·web·js
程序员笨鸟2 天前
[特殊字符] React 高频 useEffect 导致页面崩溃的真实案例:从根因排查到彻底优化
前端·javascript·学习·react.js·面试·前端框架
Highcharts.js2 天前
从旧版到新版:Highcharts for React 迁移全攻略 + 开发者必知的 5 大坑
前端·react.js·前端框架·编辑器·highcharts