单点登录(Single Sign-On, SSO)是一种身份验证机制,允许用户使用一组凭证(如用户名和密码)登录到多个相关系统,而无需为每个系统单独进行身份验证。

// stores/auth.ts
import { defineStore } from 'pinia';
import axios from 'axios';
export const useAuthStore = defineStore('auth', {
state: () => ({
token: localStorage.getItem('token') || null,
user: JSON.parse(localStorage.getItem('user') || 'null'),
}),
getters: {
isAuthenticated: (state) => !!state.token,
},
actions: {
async login(username: string, password: string) {
try {
const response = await axios.post('/api/auth/login', {
username,
password,
});
const { token, user } = response.data;
// 存储Token和用户信息
this.token = token;
this.user = user;
// 保存到localStorage
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));
// 设置axios请求头
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
return true;
} catch (error) {
this.logout();
throw error;
}
},
logout() {
this.token = null;
this.user = null;
localStorage.removeItem('token');
localStorage.removeItem('user');
delete axios.defaults.headers.common['Authorization'];
},
async validateToken() {
try {
const response = await axios.get('/api/auth/validate');
return response.data;
} catch (error) {
this.logout();
return false;
}
},
},
});// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import DashboardView from '../views/DashboardView.vue';
import LoginView from '../views/LoginView.vue';
import { useAuthStore } from '../stores/auth';
const routes = [
{
path: '/',
name: 'Home',
component: HomeView,
},
{
path: '/dashboard',
name: 'Dashboard',
component: DashboardView,
meta: {
requiresAuth: true,
},
},
{
path: '/login',
name: 'Login',
component: LoginView,
},
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});
// 全局前置守卫
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore();
// 检查路由是否需要认证
if (to.meta.requiresAuth) {
// 检查用户是否已登录
if (!authStore.isAuthenticated) {
// 未登录则重定向到登录页
next({ name: 'Login' });
} else {
// 已登录则验证Token有效性
const isValid = await authStore.validateToken();
if (!isValid) {
next({ name: 'Login' });
} else {
next();
}
}
} else {
// 不需要认证的路由直接通过
next();
}
});
export default router;// utils/axios.ts
import axios from 'axios';
import { useAuthStore } from '../stores/auth';
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 5000,
});
// 请求拦截器
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.data;
},
(error) => {
// 处理401 Unauthorized错误
if (error.response.status === 401) {
const authStore = useAuthStore();
authStore.logout();
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default service;<!-- components/Login.vue -->
<template>
<div class="login-container">
<div class="login-form">
<h2>单点登录系统</h2>
<BaseInput v-model="username" placeholder="用户名" />
<BaseInput v-model="password" type="password" placeholder="密码" />
<BaseButton @click="handleLogin">登录</BaseButton>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useAuthStore } from '@/stores/auth';
import { useRouter } from 'vue-router';
const username = ref('');
const password = ref('');
const authStore = useAuthStore();
const router = useRouter();
const handleLogin = async () => {
try {
await authStore.login(username.value, password.value);
router.push('/dashboard');
} catch (error) {
alert(error.message);
}
};
</script>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f5f5;
}
.login-form {
background-color: white;
padding: 2rem;
border-radius: 0.5rem;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 300px;
}
.login-form h2 {
margin-bottom: 1.5rem;
text-align: center;
}
.login-form input {
margin-bottom: 1rem;
width: 100%;
}
.login-form button {
width: 100%;
}
</style><!-- components/Navigation.vue -->
<template>
<nav class="bg-white shadow-md">
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div class="flex items-center">
<router-link to="/" class="font-bold text-xl">Vue SSO</router-link>
</div>
<div class="flex items-center space-x-4">
<router-link to="/" class="text-gray-600 hover:text-primary">首页</router-link>
<div v-if="isAuthenticated" class="flex items-center">
<span class="mr-2">{{ user?.username || '用户' }}</span>
<button @click="logout" class="text-gray-600 hover:text-red-500">
退出
</button>
</div>
<router-link to="/login" v-else class="text-gray-600 hover:text-primary">
登录
</router-link>
</div>
</div>
</div>
</nav>
</template>
<script setup>
import { computed } from 'vue';
import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore();
const isAuthenticated = computed(() => authStore.isAuthenticated);
const user = computed(() => authStore.user);
const logout = () => {
authStore.logout();
};
</script>// 后端CORS配置示例
app.use(cors({
origin: 'https://your-frontend-domain.com',
credentials: true,
}));// 使用HttpOnly Cookie存储Token
document.cookie = `token=${token}; path=/; domain=.your-domain.com; secure; HttpOnly`;
通过以上方案,我们实现了一个基于Vue的单点登录系统,包括:
这个方案可以作为企业级应用的单点登录基础,根据实际需求可以进一步扩展和优化,如支持多因素认证、跨域SSO等高级功能。