首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Vue 项目中登录授权功能封装的技术方案设计与实现

Vue 项目中登录授权功能封装的技术方案设计与实现

原创
作者头像
小焱
发布2025-05-31 14:22:56
发布2025-05-31 14:22:56
24200
代码可运行
举报
文章被收录于专栏:前端开发前端开发
运行总次数:0
代码可运行

Vue封装登录授权功能技术方案

一、登录授权功能概述

(一)核心概念

  • 身份验证(Authentication):确认用户身份的过程
  • 授权(Authorization):确定用户是否有权限访问特定资源
  • Token管理:安全存储和使用身份令牌
  • 会话管理:跟踪用户登录状态

(二)常见实现方式

  1. 基于Token的认证:使用JWT或OAuth2.0
  2. Cookie/Session认证:传统的服务器端会话管理
  3. 单点登录(SSO):跨应用的统一认证机制

二、Vue中封装登录授权功能

(一)项目结构设计

代码语言:txt
复制
src/
├── auth/                # 认证模块
│   ├── stores/          # 状态管理
│   │   └── auth.ts      # 认证状态
│   ├── composables/     # 组合式函数
│   │   └── useAuth.ts   # 认证逻辑
│   ├── components/      # 认证组件
│   │   ├── Login.vue    # 登录组件
│   │   └── AuthGuard.vue# 权限守卫组件
│   └── services/        # API服务
│       └── authService.ts # 认证API
├── router/              # 路由配置
│   └── index.ts         # 路由守卫
└── utils/               # 工具函数
    └── token.ts         # Token管理

(二)状态管理实现

代码语言:typescript
复制
// auth/stores/auth.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
import { authService } from '@/auth/services/authService';
import { useToken } from '@/auth/composables/useToken';

export const useAuthStore = defineStore('auth', () => {
  const router = useRouter();
  const { getToken, setToken, removeToken } = useToken();
  
  // 状态
  const user = ref(null);
  const token = ref(getToken());
  const loading = ref(false);
  
  // 获取器
  const isAuthenticated = computed(() => !!token.value);
  
  // 登录方法
  const login = async (credentials) => {
    loading.value = true;
    
    try {
      const response = await authService.login(credentials);
      const { token: newToken, user: newUser } = response;
      
      setToken(newToken);
      token.value = newToken;
      user.value = newUser;
      
      await router.push('/dashboard');
    } catch (error) {
      console.error('Login error:', error);
      throw error;
    } finally {
      loading.value = false;
    }
  };
  
  // 登出方法
  const logout = () => {
    removeToken();
    token.value = null;
    user.value = null;
    router.push('/login');
  };
  
  // 刷新用户信息
  const refreshUser = async () => {
    if (!token.value) return;
    
    try {
      const response = await authService.getUserInfo();
      user.value = response;
    } catch (error) {
      console.error('Refresh user error:', error);
      logout();
    }
  };
  
  return {
    user,
    token,
    loading,
    isAuthenticated,
    login,
    logout,
    refreshUser,
  };
});

(三)Token管理工具

代码语言:typescript
复制
// auth/composables/useToken.ts
export const useToken = () => {
  const TOKEN_KEY = 'auth_token';
  
  // 获取Token
  const getToken = () => {
    return localStorage.getItem(TOKEN_KEY);
  };
  
  // 设置Token
  const setToken = (token: string) => {
    localStorage.setItem(TOKEN_KEY, token);
  };
  
  // 移除Token
  const removeToken = () => {
    localStorage.removeItem(TOKEN_KEY);
  };
  
  return {
    getToken,
    setToken,
    removeToken,
  };
};

(四)API服务封装

代码语言:typescript
复制
// auth/services/authService.ts
import axios from '@/utils/axios';

export const authService = {
  // 登录
  login(credentials: { username: string; password: string }) {
    return axios.post('/api/auth/login', credentials);
  },
  
  // 注册
  register(userData: { username: string; password: string; email: string }) {
    return axios.post('/api/auth/register', userData);
  },
  
  // 获取用户信息
  getUserInfo() {
    return axios.get('/api/auth/user');
  },
  
  // 刷新Token
  refreshToken() {
    return axios.post('/api/auth/refresh');
  },
  
  // 登出
  logout() {
    return axios.post('/api/auth/logout');
  },
};

(五)路由守卫配置

代码语言:typescript
复制
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import { useAuthStore } from '@/auth/stores/auth';

const routes = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/auth/components/Login.vue'),
    meta: { guest: true },
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('@/views/Dashboard.vue'),
    meta: { requiresAuth: true },
  },
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('@/views/Admin.vue'),
    meta: { requiresAuth: true, roles: ['admin'] },
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

// 全局前置守卫
router.beforeEach(async (to, from, next) => {
  const authStore = useAuthStore();
  
  // 检查是否需要认证
  if (to.meta.requiresAuth) {
    // 如果未认证,重定向到登录页
    if (!authStore.isAuthenticated) {
      return next({ name: 'Login' });
    }
    
    // 检查角色权限
    if (to.meta.roles && !to.meta.roles.includes(authStore.user?.role)) {
      return next({ name: 'Forbidden' });
    }
  }
  
  // 如果是登录页且已认证,重定向到首页
  if (to.meta.guest && authStore.isAuthenticated) {
    return next({ name: 'Dashboard' });
  }
  
  // 刷新用户信息
  if (authStore.isAuthenticated && !authStore.user) {
    await authStore.refreshUser();
  }
  
  next();
});

export default router;

三、组件封装实践

(一)登录组件封装

代码语言:javascript
代码运行次数:0
运行
复制
<!-- auth/components/Login.vue -->
<template>
  <div class="login-container">
    <div class="login-card">
      <h2 class="login-title">用户登录</h2>
      
      <form @submit.prevent="handleLogin">
        <div class="form-group">
          <label for="username">用户名</label>
          <input
            type="text"
            id="username"
            v-model="form.username"
            :class="{ 'is-invalid': errors.username }"
            placeholder="请输入用户名"
          />
          <div v-if="errors.username" class="invalid-feedback">
            {{ errors.username }}
          </div>
        </div>
        
        <div class="form-group">
          <label for="password">密码</label>
          <input
            type="password"
            id="password"
            v-model="form.password"
            :class="{ 'is-invalid': errors.password }"
            placeholder="请输入密码"
          />
          <div v-if="errors.password" class="invalid-feedback">
            {{ errors.password }}
          </div>
        </div>
        
        <button
          type="submit"
          class="login-button"
          :disabled="loading"
        >
          {{ loading ? '登录中...' : '登录' }}
        </button>
      </form>
      
      <div class="login-footer">
        <router-link to="/register">注册账号</router-link>
        <router-link to="/forgot-password">忘记密码?</router-link>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';
import { useAuthStore } from '@/auth/stores/auth';

const authStore = useAuthStore();

// 表单数据
const form = reactive({
  username: '',
  password: '',
});

// 错误信息
const errors = reactive({
  username: '',
  password: '',
});

// 加载状态
const loading = ref(false);

// 登录处理
const handleLogin = async () => {
  // 重置错误
  errors.username = '';
  errors.password = '';
  
  // 简单验证
  if (!form.username) {
    errors.username = '请输入用户名';
    return;
  }
  
  if (!form.password) {
    errors.password = '请输入密码';
    return;
  }
  
  loading.value = true;
  
  try {
    await authStore.login(form);
  } catch (error: any) {
    if (error.response && error.response.status === 401) {
      errors.password = '用户名或密码错误';
    } else {
      errors.common = '登录失败,请稍后重试';
    }
  } finally {
    loading.value = false;
  }
};
</script>

(二)权限守卫组件

代码语言:javascript
代码运行次数:0
运行
复制
<!-- auth/components/AuthGuard.vue -->
<template>
  <div v-if="hasAccess">
    <slot />
  </div>
  <div v-else>
    <div v-if="!isAuthenticated" class="auth-message">
      <p>请先登录以访问此页面</p>
      <button @click="redirectToLogin">登录</button>
    </div>
    <div v-else class="auth-message">
      <p>您没有权限访问此页面</p>
      <button @click="redirectToHome">返回首页</button>
    </div>
  </div>
</template>

<script setup>
import { computed } from 'vue';
import { useAuthStore } from '@/auth/stores/auth';
import { useRouter } from 'vue-router';

const props = defineProps({
  roles: {
    type: Array,
    default: () => [],
  },
});

const authStore = useAuthStore();
const router = useRouter();

// 是否已认证
const isAuthenticated = computed(() => authStore.isAuthenticated);

// 是否有权限访问
const hasAccess = computed(() => {
  if (!isAuthenticated.value) return false;
  if (!props.roles.length) return true;
  
  return props.roles.includes(authStore.user?.role);
});

// 重定向到登录页
const redirectToLogin = () => {
  router.push({ name: 'Login' });
};

// 重定向到首页
const redirectToHome = () => {
  router.push({ name: 'Home' });
};
</script>

四、在应用中使用

(一)在页面中使用权限守卫

代码语言:javascript
代码运行次数:0
运行
复制
<!-- views/Admin.vue -->
<template>
  <div>
    <AuthGuard roles="['admin']">
      <h1>管理员面板</h1>
      <p>这里是只有管理员可以访问的内容</p>
    </AuthGuard>
  </div>
</template>

<script setup>
import AuthGuard from '@/auth/components/AuthGuard.vue';
</script>

(二)在模板中控制元素显示

代码语言:javascript
代码运行次数:0
运行
复制
<template>
  <div>
    <button v-if="isAdmin" @click="manageUsers">管理用户</button>
    
    <AuthGuard roles="['editor']">
      <button @click="editArticle">编辑文章</button>
    </AuthGuard>
  </div>
</template>

<script setup>
import { computed } from 'vue';
import { useAuthStore } from '@/auth/stores/auth';
import AuthGuard from '@/auth/components/AuthGuard.vue';

const authStore = useAuthStore();

const isAdmin = computed(() => authStore.user?.role === 'admin');
</script>

五、安全增强措施

(一)Token刷新机制

代码语言:typescript
复制
// auth/composables/useRefreshToken.ts
import { ref, onMounted, watch } from 'vue';
import { useAuthStore } from '@/auth/stores/auth';
import { authService } from '@/auth/services/authService';

export const useRefreshToken = () => {
  const authStore = useAuthStore();
  const refreshTimer = ref<NodeJS.Timeout | null>(null);
  
  // 刷新Token
  const refreshToken = async () => {
    if (!authStore.isAuthenticated) return;
    
    try {
      const response = await authService.refreshToken();
      const { token } = response;
      
      authStore.token = token;
      localStorage.setItem('auth_token', token);
      
      // 设置下一次刷新时间
      setRefreshTimer();
    } catch (error) {
      console.error('Token refresh failed:', error);
      authStore.logout();
    }
  };
  
  // 设置刷新定时器
  const setRefreshTimer = () => {
    if (refreshTimer.value) {
      clearTimeout(refreshTimer.value);
    }
    
    // 假设Token有效期为1小时,提前10分钟刷新
    const expiresIn = 60 * 60 * 1000; // 1小时
    const refreshBefore = 10 * 60 * 1000; // 10分钟
    
    refreshTimer.value = setTimeout(refreshToken, expiresIn - refreshBefore);
  };
  
  onMounted(() => {
    if (authStore.isAuthenticated) {
      setRefreshTimer();
    }
  });
  
  watch(() => authStore.isAuthenticated, (newValue) => {
    if (newValue) {
      setRefreshTimer();
    } else if (refreshTimer.value) {
      clearTimeout(refreshTimer.value);
      refreshTimer.value = null;
    }
  });
  
  return {
    refreshToken,
  };
};

(二)Axios拦截器增强

代码语言:typescript
复制
// utils/axios.ts
import axios from 'axios';
import { useAuthStore } from '@/auth/stores/auth';
import { useRefreshToken } from '@/auth/composables/useRefreshToken';

const service = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000,
});

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    const authStore = useAuthStore();
    
    if (authStore.token) {
      config.headers.Authorization = `Bearer ${authStore.token}`;
    }
    
    return config;
  },
  (error) => {
    console.error('Request error:', error);
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    return response;
  },
  async (error) => {
    const originalRequest = error.config;
    const authStore = useAuthStore();
    const { refreshToken } = useRefreshToken();
    
    // 如果响应状态码是401(未授权)并且还没有重试
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      try {
        // 刷新Token
        await refreshToken();
        
        // 使用新的Token重试请求
        return service(originalRequest);
      } catch (refreshError) {
        // 刷新Token失败,跳转到登录页
        authStore.logout();
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);

export default service;

六、总结

通过以上方案,我们实现了一个完整的Vue登录授权功能封装,包括:

  1. 模块化设计:将认证相关功能组织到独立的模块中
  2. 状态管理:使用Pinia管理用户认证状态
  3. 组件封装:创建可复用的登录组件和权限守卫
  4. 路由守卫:保护需要认证的路由
  5. Token管理:安全地存储和使用身份令牌
  6. 安全增强:实现Token刷新机制和请求拦截器

这种封装方式使登录授权功能在Vue应用中变得清晰、可维护,并且可以轻松扩展以满足不同项目的需求。


Vue, 登录授权,功能封装,技术方案,前端开发,身份验证,JWT,Token, 路由守卫,状态管理,Vue Router,Vuex, 权限控制,单点登录,前端安全



资源地址:

https://pan.quark.cn/s/dc62840f6d67


原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Vue封装登录授权功能技术方案
    • 一、登录授权功能概述
      • (一)核心概念
      • (二)常见实现方式
    • 二、Vue中封装登录授权功能
      • (一)项目结构设计
      • (二)状态管理实现
      • (三)Token管理工具
      • (四)API服务封装
      • (五)路由守卫配置
    • 三、组件封装实践
      • (一)登录组件封装
      • (二)权限守卫组件
    • 四、在应用中使用
      • (一)在页面中使用权限守卫
      • (二)在模板中控制元素显示
    • 五、安全增强措施
      • (一)Token刷新机制
      • (二)Axios拦截器增强
    • 六、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档