npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ztzx/vue-sso-interceptor

v1.3.0

Published

Vue 3 SSO authentication interceptor plugin with Casdoor support

Readme

@ztzx/vue-sso-interceptor

Vue 3 SSO 认证拦截器插件,支持 Casdoor OAuth2/OIDC 认证流程。

功能说明

@ztzx/vue-sso-interceptor 是一个专为 Vue 3 应用设计的 SSO(单点登录)认证拦截器插件,提供了完整的 OAuth2/OIDC 认证流程支持。该插件基于 Casdoor 认证平台,为 Vue 应用提供开箱即用的 SSO 解决方案。

核心功能

  1. 完整的 SSO 认证流程

    • 支持 Casdoor OAuth2/OIDC 标准认证流程
    • 自动处理授权码回调
    • 支持静默授权(Silent Authentication)
    • 支持强制登录模式
  2. 自动路由守卫

    • 基于 Vue Router 的导航守卫
    • 自动保护需要认证的路由
    • 支持公开路由和回调路由配置
    • 自动处理未登录用户的重定向
  3. Token 管理

    • Token 存储在 http-only Cookie 中,提高安全性
    • 自动刷新 Access Token
    • 支持并发请求时的 Token 刷新队列
    • 自动处理 Token 过期情况
  4. 状态管理

    • 基于 Pinia 的响应式状态管理
    • 提供 Composition API Hook(useAuth
    • 支持本地存储持久化
  5. 跨应用 SSO

    • 支持应用间跳转和认证状态共享
    • 通过 fromApp 参数标识来源应用
    • 自动处理跨应用的登录流程
  6. 高度可配置

    • 支持插件级别配置
    • 支持路由守卫级别配置
    • 支持环境变量配置
    • 支持自定义 Axios 实例
  7. TypeScript 支持

    • 完整的 TypeScript 类型定义
    • 提供类型导出,方便类型检查

安装

npm install @ztzx/vue-sso-interceptor
# 或
yarn add @ztzx/vue-sso-interceptor
# 或
pnpm add @ztzx/vue-sso-interceptor

依赖要求

确保你的项目已安装以下 peer dependencies:

npm install vue@^3.5.0 vue-router@^4.0.0 pinia@^3.0.0 axios@^1.0.0

使用说明

1. 基础使用

1.1 在 main.ts 中安装插件

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createRouter } from 'vue-router'
import VueSsoInterceptor from '@ztzx/vue-sso-interceptor'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()
const router = createRouter({
  // ... 你的路由配置
})

app.use(pinia)
app.use(router)

// 安装 SSO 插件
app.use(VueSsoInterceptor, {
  backendBaseUrl: import.meta.env.VITE_BACKEND_BASE_URL,
  appId: import.meta.env.VITE_APP_ID,
  appName: import.meta.env.VITE_APP_NAME,
  loginPath: '/login/casdoor',
  callbackPath: '/callback/casdoor',
  callbackApiPath: '/api/travel-plan', // 业务特定的回调 API 路径
  publicRoutes: ['/login/casdoor'],
})

app.mount('#app')

1.2 配置路由守卫

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { createAuthGuard } from '@ztzx/vue-sso-interceptor'
import HomeView from '@/views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView,
    },
    {
      path: '/login/casdoor',
      name: 'login',
      component: () => import('@/views/LoginView.vue'),
    },
    {
      path: '/callback/casdoor',
      name: 'casdoor-callback',
      component: () => import('@/views/CasdoorCallbackView.vue'),
    },
    {
      path: '/dashboard',
      name: 'dashboard',
      component: () => import('@/views/Dashboard.vue'),
      meta: { requiresAuth: true }
    },
  ],
})

// 添加认证守卫
router.beforeEach(createAuthGuard({
  publicRoutes: ['/login/casdoor'],
  callbackRoutes: ['/callback/casdoor'],
  refreshTimeout: 3000, // 刷新 token 超时时间(毫秒)
}))

export default router

1.3 创建回调页面组件

<!-- views/CasdoorCallbackView.vue -->
<template>
  <div>
    <p v-if="processing">正在处理回调...</p>
    <p v-else-if="error">错误: {{ error }}</p>
    <p v-else>登录成功!</p>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { fetchCasdoorCallback, useAuth } from '@ztzx/vue-sso-interceptor'

const route = useRoute()
const router = useRouter()
const { setAuth } = useAuth()
const processing = ref(false)
const error = ref('')

onMounted(async () => {
  const code = route.query.code as string
  const state = route.query.state as string
  
  if (code && state) {
    processing.value = true
    try {
      const query = new URLSearchParams({ code, state }).toString()
      const data = await fetchCasdoorCallback(query)
      setAuth(data.token, data.user, data.accessToken)
      router.replace('/')
    } catch (e) {
      error.value = e instanceof Error ? e.message : '未知错误'
    } finally {
      processing.value = false
    }
  }
})
</script>

2. 在组件中使用

2.1 使用 Composition API Hook

<template>
  <div>
    <div v-if="isAuthenticated">
      <p>欢迎,{{ user?.name }}</p>
      <button @click="handleLogout">退出登录</button>
    </div>
    <div v-else>
      <button @click="handleLogin">登录</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { useAuth } from '@ztzx/vue-sso-interceptor'
import { useRouter } from 'vue-router'

const { isAuthenticated, user, logout } = useAuth()
const router = useRouter()

const handleLogin = () => {
  router.push('/login/casdoor')
}

const handleLogout = async () => {
  await logout()
  // 退出后重定向到登录页,触发 SSO 重新登录
  window.location.href = '/login/casdoor'
}
</script>

2.2 使用 Pinia Store

<script setup lang="ts">
import { useAuthStore } from '@ztzx/vue-sso-interceptor'
import { computed } from 'vue'

const auth = useAuthStore()

// 初始化(从本地存储恢复状态)
auth.initFromStorage()

const isAuthenticated = computed(() => !!auth.accessToken)
const userName = computed(() => auth.user?.name || '未登录')

const handleLogout = async () => {
  await auth.clear()
  window.location.href = '/login/casdoor'
}
</script>

3. 跨应用 SSO

import { jumpToApp } from '@ztzx/vue-sso-interceptor'

// 跳转到其他应用,自动传递 fromApp 参数
jumpToApp('http://app-b.example.com', 'appA')

详细配置

AuthConfig(插件配置)

在安装插件时传入的配置选项:

interface AuthConfig {
  /** 
   * 后端基础 URL
   * - 如果设置了则使用完整 URL(如:http://api.example.com)
   * - 如果不设置则使用相对路径(通过 nginx 代理)
   * - 支持环境变量:VITE_BACKEND_BASE_URL
   */
  backendBaseUrl?: string

  /** 
   * 应用标识
   * - 用于 localStorage 存储前缀,避免多应用冲突
   * - 默认值:'auth_interceptor'
   * - 支持环境变量:VITE_APP_ID
   */
  appId?: string

  /** 
   * 应用名称
   * - 备用标识,如果 appId 未设置则使用此值
   * - 支持环境变量:VITE_APP_NAME
   */
  appName?: string

  /** 
   * 登录路径
   * - 前端登录页面路径
   * - 默认值:'/login/casdoor'
   */
  loginPath?: string

  /** 
   * 回调路径
   * - Casdoor 回调后的前端路由路径
   * - 默认值:'/callback/casdoor'
   */
  callbackPath?: string

  /** 
   * 回调 API 路径
   * - 后端处理 Casdoor 回调的 API 路径
   * - 默认值:'/api/auth/callback'
   * - 重要:业务应用需要根据实际后端 API 路径配置此值
   * - 支持环境变量:VITE_CALLBACK_API_PATH
   * 
   * 示例:
   * - '/api/travel-plan' - 旅游业务应用
   * - '/api/order/callback' - 订单业务应用
   */
  callbackApiPath?: string

  /** 
   * 公开路由列表
   * - 这些路由不需要认证即可访问
   * - 默认值:['/login/casdoor']
   */
  publicRoutes?: string[]

  /** 
   * 自定义 Axios 实例
   * - 如果不提供则使用默认的 axios
   * - 可以用于配置请求拦截器、响应拦截器等
   */
  axiosInstance?: AxiosInstance
}

GuardOptions(路由守卫配置)

在创建路由守卫时传入的配置选项:

interface GuardOptions {
  /** 
   * 公开路由列表
   * - 这些路由不需要认证即可访问
   * - 会与插件配置中的 publicRoutes 合并
   */
  publicRoutes?: string[]

  /** 
   * 回调路由路径列表
   * - 这些路由在带 code 和 state 参数时不需要认证
   * - 默认值:[config.callbackPath]
   * - 用于处理 Casdoor 回调
   */
  callbackRoutes?: string[]

  /** 
   * 登录路径
   * - 默认从插件配置中获取
   * - 可以在此处覆盖
   */
  loginPath?: string

  /** 
   * 刷新 token 超时时间(毫秒)
   * - 默认不设置超时
   * - 如果设置了,刷新 token 超过此时间会失败
   */
  refreshTimeout?: number

  /** 
   * 是否允许首页在未登录时访问
   * - 默认值:false
   * - 如果设置为 true,路径为 '/' 的路由在未登录时也可以访问
   */
  allowHomeWithoutAuth?: boolean
}

环境变量配置

支持通过环境变量配置(Vite 项目):

# .env
VITE_BACKEND_BASE_URL=http://localhost:8080
VITE_APP_ID=my-app
VITE_APP_NAME=my-app
VITE_CALLBACK_API_PATH=/api/travel-plan

配置优先级:用户配置 > 环境变量 > 默认配置

配置示例

示例 1:基础配置

app.use(VueSsoInterceptor, {
  backendBaseUrl: 'http://localhost:8080',
  appId: 'my-app',
  loginPath: '/login/casdoor',
  callbackPath: '/callback/casdoor',
  callbackApiPath: '/api/auth/callback',
  publicRoutes: ['/login/casdoor', '/about'],
})

示例 2:使用环境变量

app.use(VueSsoInterceptor, {
  backendBaseUrl: import.meta.env.VITE_BACKEND_BASE_URL,
  appId: import.meta.env.VITE_APP_ID,
  appName: import.meta.env.VITE_APP_NAME,
  callbackApiPath: import.meta.env.VITE_CALLBACK_API_PATH || '/api/auth/callback',
  loginPath: '/login/casdoor',
  callbackPath: '/callback/casdoor',
})

示例 3:自定义 Axios 实例

import axios from 'axios'

const axiosInstance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
})

// 添加请求拦截器
axiosInstance.interceptors.request.use(
  (config) => {
    // 添加自定义请求头等
    return config
  },
  (error) => Promise.reject(error)
)

app.use(VueSsoInterceptor, {
  axiosInstance,
  backendBaseUrl: 'https://api.example.com',
  appId: 'my-app',
})

示例 4:路由守卫配置

router.beforeEach(createAuthGuard({
  publicRoutes: ['/login/casdoor', '/about', '/help'],
  callbackRoutes: ['/callback/casdoor', '/callback/oauth'],
  allowHomeWithoutAuth: true,
  refreshTimeout: 5000,
}))

API 文档

Store API

useAuthStore

Pinia store,管理认证状态。

import { useAuthStore } from '@ztzx/vue-sso-interceptor'

const auth = useAuthStore()

// 状态属性
auth.token        // Token(存储在 http-only Cookie 中,这里仅用于状态管理)
auth.accessToken  // Access Token
auth.user         // 用户信息(UserInfo 类型)
auth.loading      // 加载状态
auth.error        // 错误信息

// 方法
auth.initFromStorage()                    // 从本地存储初始化状态
auth.setAuth(token, user, accessToken)    // 设置认证信息
auth.setUser(user)                        // 设置用户信息
auth.clear()                              // 清除认证信息
auth.setLoading(flag)                     // 设置加载状态
auth.setError(message)                    // 设置错误信息

API 服务

fetchCasdoorCallback

处理 Casdoor 回调,将授权码换取 Token。

import { fetchCasdoorCallback } from '@ztzx/vue-sso-interceptor'

/**
 * @param queryString - URL 查询字符串(如:'code=xxx&state=xxx')
 * @returns Promise<AuthResponse> - { token, accessToken, user }
 */
const data = await fetchCasdoorCallback('code=xxx&state=xxx')

fetchUserInfo

获取用户信息。

import { fetchUserInfo } from '@ztzx/vue-sso-interceptor'

/**
 * @param token - Token(可选,从 Cookie 中自动获取)
 * @param accessToken - Access Token
 * @returns Promise<UserInfo>
 */
const userInfo = await fetchUserInfo(null, accessToken)

refreshAccessToken

刷新 Access Token。

import { refreshAccessToken } from '@ztzx/vue-sso-interceptor'

/**
 * @returns Promise<RefreshTokenResponse> - { accessToken, expiresIn? }
 */
const result = await refreshAccessToken()

logout

退出登录。

import { logout } from '@ztzx/vue-sso-interceptor'

/**
 * @param token - Token(可选,从 Cookie 中自动获取)
 * @param accessToken - Access Token
 * @returns Promise<void>
 */
await logout(null, accessToken)

fetchPing

健康检查。

import { fetchPing } from '@ztzx/vue-sso-interceptor'

const result = await fetchPing()

setupAxiosInterceptor

设置 Axios 响应拦截器,自动处理 401 错误。

import { setupAxiosInterceptor } from '@ztzx/vue-sso-interceptor'
import axios from 'axios'

const axiosInstance = axios.create()
setupAxiosInterceptor(axiosInstance, '/login/casdoor')

Composables

useAuth

Composition API Hook,提供响应式的认证状态和便捷方法。

import { useAuth } from '@ztzx/vue-sso-interceptor'

const {
  // 响应式状态
  isAuthenticated,  // boolean - 是否已登录
  user,             // Ref<UserInfo | null> - 用户信息
  accessToken,      // Ref<string | null> - Access Token
  loading,          // Ref<boolean> - 加载状态
  error,            // Ref<string | null> - 错误信息
  
  // 方法
  initAuth,         // () => void - 初始化认证状态
  setAuth,          // (token, user, accessToken) => void - 设置认证信息
  setUser,          // (user) => void - 设置用户信息
  getUserInfo,      // () => Promise<UserInfo> - 获取用户信息(从服务器)
  refreshToken,     // () => Promise<void> - 刷新 Token
  logout,           // () => Promise<void> - 退出登录
  clear,            // () => void - 清除认证信息
} = useAuth()

工具函数

jumpToApp

跨应用跳转(跨应用 SSO)。

import { jumpToApp } from '@ztzx/vue-sso-interceptor'

/**
 * @param targetUrl - 目标应用 URL
 * @param fromAppId - 来源应用标识
 */
jumpToApp('http://app-b.example.com', 'appA')

getCurrentAppId

获取当前应用标识。

import { getCurrentAppId } from '@ztzx/vue-sso-interceptor'

const appId = getCurrentAppId()

类型定义

所有类型都已导出,可以直接使用:

import type {
  UserInfo,              // 用户信息接口
  AuthState,            // 认证状态接口
  AuthConfig,           // 插件配置接口
  AuthResponse,         // 认证响应接口
  RefreshTokenResponse, // 刷新 Token 响应接口
  GuardOptions,         // 路由守卫配置接口
} from '@ztzx/vue-sso-interceptor'

实现方式

架构设计

插件采用模块化设计,主要包含以下模块:

src/
├── index.ts              # 插件入口,导出所有公共 API
├── types.ts              # TypeScript 类型定义
├── config.ts             # 配置管理模块
├── store/
│   └── auth.ts           # Pinia Store(状态管理)
├── services/
│   └── authApi.ts        # API 服务(HTTP 请求)
├── router/
│   └── guard.ts          # 路由守卫
├── composables/
│   └── useAuth.ts        # Composition API Hook
└── utils/
    └── appNavigation.ts  # 工具函数(跨应用跳转等)

核心实现

1. 配置管理(config.ts)

配置管理采用三层优先级机制:

  1. 默认配置:提供合理的默认值
  2. 环境变量配置:从 Vite 环境变量读取
  3. 用户配置:插件安装时传入的配置

配置合并逻辑:

// 合并顺序:用户配置 > 环境变量 > 默认配置
globalConfig = {
  ...DEFAULT_CONFIG,
  ...envConfig,
  ...config,
}

关键设计getConfig() 函数在配置未初始化时,返回默认配置但不修改 globalConfig,避免在插件初始化前(如路由守卫创建时)重置用户配置。

2. 状态管理(store/auth.ts)

基于 Pinia 实现响应式状态管理:

  • 状态持久化:使用 localStorage 存储 accessTokenuser 信息
  • 存储键隔离:通过 appId 前缀避免多应用冲突
  • 状态同步:提供 initFromStorage() 方法从本地存储恢复状态

3. API 服务(services/authApi.ts)

封装所有与后端交互的 HTTP 请求:

  • Token 管理:Token 存储在 http-only Cookie 中,提高安全性
  • 自动刷新:实现并发请求时的 Token 刷新队列机制
  • 错误处理:统一处理 401 错误,自动跳转登录页

Token 刷新队列实现

let isRefreshing = false
let failedQueue: Array<{ resolve, reject }> = []

// 当检测到 401 错误时
if (error.response?.status === 401 && !isRefreshing) {
  isRefreshing = true
  // 刷新 Token
  // 成功后处理队列中的所有请求
  // 失败后拒绝队列中的所有请求
}

4. 路由守卫(router/guard.ts)

基于 Vue Router 的 beforeEach 导航守卫实现:

路由检查流程

  1. 公开路由检查:如果是公开路由,直接放行
  2. 回调路由检查:如果是回调路由且带有 codestate 参数,放行
  3. 登录页处理:如果是登录页,检测 fromAppforceLogin 参数
  4. 跨应用跳转处理:检测 fromApp 参数,处理跨应用 SSO
  5. Token 检查:检查 accessToken,如果没有则尝试刷新
  6. 自动刷新:如果刷新成功,保存新的 accessToken 并放行
  7. 跳转登录:如果刷新失败,清除状态并跳转到登录页

静默授权支持

  • 默认使用静默授权(prompt=none
  • 如果用户在 Casdoor 已登录,自动完成登录
  • 如果静默授权失败(error=login_required),重定向到正常登录流程

5. Composition API Hook(composables/useAuth.ts)

提供响应式的认证状态和便捷方法:

  • 基于 Pinia Store 封装
  • 提供响应式的 isAuthenticateduseraccessToken 等状态
  • 封装常用的认证操作方法

6. 跨应用 SSO(utils/appNavigation.ts)

实现应用间跳转和认证状态共享:

  • 通过 URL 参数 fromApp 标识来源应用
  • 目标应用检测到 fromApp 参数时,使用跨应用 SSO 流程
  • 支持强制登录模式(forceLogin=true

工作流程

登录流程

  1. 用户访问需要认证的页面
  2. 路由守卫检测到未登录,重定向到 /login/casdoor
  3. 前端跳转到后端登录接口(携带 fromAppforceLogin 等参数)
  4. 后端重定向到 Casdoor 授权页面
  5. 用户在 Casdoor 完成登录
  6. Casdoor 回调到后端(携带 codestate
  7. 后端处理回调,设置 http-only Cookie,重定向到前端回调页面
  8. 前端回调页面调用 fetchCasdoorCallback() 获取用户信息
  9. 保存 accessTokenuser 到 Store 和 localStorage
  10. 重定向到原始目标页面

Token 刷新流程

  1. API 请求返回 401 错误
  2. Axios 响应拦截器捕获错误
  3. 检查是否正在刷新(isRefreshing
  4. 如果未在刷新,发起刷新请求,并将当前请求加入队列
  5. 如果正在刷新,将当前请求加入队列
  6. 刷新成功后,处理队列中的所有请求
  7. 刷新失败后,拒绝队列中的所有请求,跳转登录页

跨应用 SSO 流程

  1. 应用 A 调用 jumpToApp('http://app-b.com', 'appA')
  2. 跳转到应用 B,URL 携带 fromApp=appA 参数
  3. 应用 B 的路由守卫检测到 fromApp 参数
  4. 如果用户未登录,跳转到登录页(保留 fromApp 参数)
  5. 后端检测到 fromApp 参数,使用跨应用 SSO 流程
  6. 如果用户在 Casdoor 已登录,自动完成登录
  7. 登录成功后,应用 B 可以获取用户信息

安全考虑

  1. Token 存储:Token 存储在 http-only Cookie 中,防止 XSS 攻击
  2. Access Token:前端只存储 accessToken,用于 API 请求
  3. 自动刷新:Token 过期时自动刷新,无需用户重新登录
  4. 并发控制:Token 刷新时使用队列机制,避免并发刷新
  5. 错误处理:统一的错误处理机制,确保安全性

配置初始化时机

关键设计:插件支持在路由守卫创建时(模块加载时)调用 getConfig(),此时配置可能尚未初始化。为了解决这个问题:

  • getConfig() 在配置未初始化时,返回默认配置但不修改 globalConfig
  • 这样即使路由守卫在插件初始化之前被调用,也不会影响后续的配置初始化
  • 插件初始化时调用 initConfig(options) 会正确设置用户配置

这种设计确保了配置的灵活性和可靠性。

注意事项

  1. Token 存储:Token 存储在 http-only Cookie 中,前端无法直接读取,只能通过 accessToken 判断认证状态。

  2. 路由守卫顺序:路由守卫必须在路由配置后添加,确保 Pinia 和 Vue Router 已安装。

  3. 回调 API 路径callbackApiPath 必须根据实际后端 API 路径配置,不同业务应用可能有不同的路径。

  4. 跨应用 SSO:使用 jumpToApp 函数实现跨应用跳转,目标应用需要支持 fromApp 参数。

  5. 静默授权:默认支持静默授权(prompt=none),如果用户在 Casdoor 已登录,将自动完成登录。

  6. 自动刷新:当 accessToken 过期时,会自动尝试刷新,如果刷新失败会跳转到登录页。

  7. 环境变量:如果使用环境变量配置,确保变量名以 VITE_ 开头(Vite 项目要求)。

  8. TypeScript:插件提供完整的 TypeScript 类型定义,建议在 TypeScript 项目中使用以获得更好的类型检查。

许可证

MIT