3084 字
15 分钟

前端分析:Vue 管理后台如何承接 SSO 权限系统

前端分析:Vue 管理后台如何承接 SSO 权限系统#

前端不是这个项目里最复杂的协议层,但它决定了系统是否真的可用。SSO 后端提供了大量能力:用户、用户组、角色、权限、应用、审计、安全策略、会话。如果前端只是把接口简单铺成表格,管理员仍然很难理解权限关系。

这个管理后台使用 Vue 3 + Vite + Pinia + Tailwind CSS,实现了一个偏工具型的后台界面。

前端目录结构:

frontend/
├── src/
│ ├── App.vue
│ ├── main.js
│ ├── router/
│ ├── stores/
│ ├── services/
│ ├── views/
│ ├── components/
│ ├── utils/
│ └── assets/
├── vite.config.js
├── tailwind.config.js
└── package.json

技术栈#

依赖比较克制:

  • vue:页面和组件。
  • vue-router:路由。
  • pinia:登录态和用户信息。
  • axios:请求管理后台 API。
  • lucide-vue-next:图标。
  • tailwindcss:样式。

项目没有引入大型 UI 组件库,而是自己封装了几个通用组件:

  • PageHeader
  • TableBlock
  • Drawer
  • Modal
  • EntityForm
  • PermissionMatrix
  • ScopedChecklist
  • SelectBlock
  • StatusPill
  • FullScreenEditor

这让页面风格统一,也避免了为一个个人项目引入过重的后台框架。

应用入口#

main.js 做的事很少:

  1. 创建 Vue app。
  2. 创建 Pinia。
  3. 注册 router。
  4. 初始化 auth store。
  5. 挂载应用。

App.vue 负责整体布局:

  • 未初始化时显示加载状态。
  • 未登录时直接渲染当前路由,例如登录页、授权确认页。
  • 已登录时渲染后台框架:顶部栏、侧边导航、内容区域、移动端抽屉导航。

后台页面结构是典型的管理台布局:

┌─────────────────────────── 顶部栏 ───────────────────────────┐
│ SSO Admin 刷新 / 当前用户 │
├───────────────┬───────────────────────────────────────────────┤
│ 侧边菜单 │ 当前页面 │
│ Overview │ │
│ Users │ │
│ Groups │ │
│ Roles │ │
│ ... │ │
└───────────────┴───────────────────────────────────────────────┘

登录态设计#

登录态集中在 stores/authStore.js

state 包括:

  • initialized
  • accessToken
  • refreshToken
  • user

这些数据会同步到 localStorage:

sso_access_token
sso_refresh_token
sso_user

初始化时,如果本地有 access token:

  • token 已过期:清理本地状态。
  • token 未过期:调用 /api/auth/me 拉取最新用户信息。
  • 请求失败:清理本地状态。

这一步很重要。JWT 里虽然有用户信息,但系统存在 session 状态、权限版本和账号状态。前端启动时重新请求 /me,可以尽早发现后端已经撤销会话或更新权限。

Axios 拦截器#

services/api.js 封装 axios 实例。

请求拦截器会自动添加:

Authorization: Bearer <accessToken>

响应拦截器处理 401:

  1. 读取 localStorage 中的 refresh token。
  2. 如果当前请求不是重试请求,则调用 /api/auth/refresh
  3. 使用返回的新 token 更新 localStorage。
  4. 触发 sso:token-refreshed 浏览器事件,同步 Pinia store。
  5. 重试原始请求。
  6. 如果刷新失败,清理本地状态并触发 sso:session-expired

这里还有一个并发控制:

let refreshing = null

当多个请求同时收到 401 时,前端不会发起多个 refresh 请求,而是复用同一个 refreshing Promise。这能避免 refresh token rotation 下的典型问题:多个并发刷新请求会让第一个请求把 refresh token 标记为 used,后续请求再拿旧 token 刷新就可能触发复用检测。

这是这个前端实现里很值得保留的细节。

路由与权限守卫#

路由定义在 router/index.js

const routes = [
{ path: '/login', component: () => import('../views/LoginView.vue'), meta: { requiresAuth: false } },
{ path: '/consent', component: () => import('../views/ConsentView.vue'), meta: { requiresAuth: false } },
{ path: '/', redirect: '/overview' },
{ path: '/overview', component: () => import('../views/OverviewView.vue') },
{ path: '/profile', component: () => import('../views/ProfileView.vue') },
{ path: '/users', component: () => import('../views/UsersView.vue') },
{ path: '/groups', component: () => import('../views/GroupsView.vue') },
{ path: '/roles', component: () => import('../views/RolesView.vue') },
{ path: '/permissions', component: () => import('../views/PermissionsView.vue') },
{ path: '/clients', component: () => import('../views/ClientsView.vue') },
{ path: '/sessions', component: () => import('../views/SessionsView.vue') },
{ path: '/audit', component: () => import('../views/AuditView.vue') },
{ path: '/security', component: () => import('../views/SecurityView.vue') },
]

守卫逻辑分三层:

  • 未登录访问受保护页面:跳转到 /login?returnTo=...
  • 平台管理页面只允许平台管理员访问,例如 /clients/audit/security
  • 应用管理页面允许平台管理员或应用管理员访问,例如 /users/groups/roles/permissions

前端权限工具在 utils/permissions.js,它复刻了后端的 permissionImplies()

平台管理员判断:

export function isPlatformAdmin(permissions) {
return hasPermission(permissions, 'sso.admin')
}

应用管理员判断:

export function managedApps(permissions) {
if (isPlatformAdmin(permissions)) return null
const apps = new Set()
for (const permission of permissions || []) {
const parts = String(permission || '').split('.').filter(Boolean)
if (parts[0] && parts[1] === 'admin' && parts[0] !== 'sso') apps.add(parts[0])
}
return [...apps].sort()
}

注意:前端守卫只是用户体验层,真正的安全边界仍然在后端 requireAdminAuth()requireScopedManagement()

菜单可见性#

App.vue 根据当前用户权限计算 visibleNav

  • 平台管理员:显示全部菜单。
  • 应用管理员:隐藏应用接入、审计日志、安全策略。
  • 普通用户:只显示个人中心和会话。

这个设计和后端范围控制一致:

用户类型可见页面
平台管理员全部页面。
应用管理员概览、个人中心、会话、用户、用户组、角色、权限。
普通用户个人中心、会话。

应用管理员仍能进入用户、用户组、角色、权限页,但接口会自动限制到自己拥有 {app}.admin 的应用范围。

登录页#

LoginView.vue 调用 auth.login(),后者请求 /api/auth/login

登录失败时,后端可能返回:

  • unauthorized
  • captcha_required
  • invalid_captcha
  • account_locked
  • forbidden

Axios 拦截器会对通用错误码做映射,但对验证码相关错误保持原始结构,让登录页可以读取 captchaIdcaptchaImage

成功登录后,页面会根据 returnTo 回到原目标地址。这同样服务于 OAuth 授权流程:业务系统跳到 /oauth/authorize,SSO 判断未登录后重定向到前端登录页,登录完成再回到原授权地址继续流程。

授权确认页#

ConsentView.vue 对应 OAuth consent。

它从 URL query 中读取:

  • client_id
  • redirect_uri
  • scope
  • state
  • code_challenge
  • code_challenge_method
  • audience

用户同意时,前端调用:

POST /oauth/consent

后端返回最终 redirect URL,前端再跳回业务系统。用户拒绝时,会带着 access_denied 回到业务系统。

这里前端只负责展示和提交决定。client、redirect URI、scope、audience、PKCE 的最终校验仍在后端。

概览页#

OverviewView.vue 根据用户类型展示不同数据:

平台管理员看到:

  • 用户数量。
  • 用户组数量。
  • 应用数量。
  • 失败登录数量。
  • 最近登录。
  • 最近审计。

应用管理员看到:

  • 当前应用范围内的用户数量。
  • 用户组数量。
  • 角色数量。
  • 权限数量。

这体现了项目对“平台管理”和“应用管理”的区分。平台管理员关心全局安全和接入;应用管理员关心自己应用下的人和权限。

应用接入页#

ClientsView.vue 只对平台管理员开放。

它管理 OAuth client 的核心字段:

  • clientKey
  • name
  • clientType
  • audience
  • redirectUris
  • postLogoutRedirectUris
  • allowedScopes
  • servicePermissions
  • tokenTtlSeconds
  • refreshTtlSeconds
  • status

创建应用后,如果后端返回一次性 secret 或应用管理员账号密码,前端用 alert 展示:

  • Client Secret 只显示一次。
  • 应用管理员用户名和密码只显示一次。

这类敏感信息不应在后端持久保存明文,也不应该后续再次查询显示。当前实现符合这个原则。

页面还直接显示接入地址:

  • Discovery
  • Authorize
  • JWKS

这对接入方很实用,避免管理员每次去翻文档。

用户、用户组、角色、权限管理#

这几类页面结构相似,但各自有不同编辑关系:

  • 用户:基础信息、状态、角色、权限、用户组。
  • 用户组:基础信息、成员、角色、权限。
  • 角色:基础信息、权限。
  • 权限:权限字典。

它们共同使用了几个组件。

FullScreenEditor#

用户、用户组、角色编辑使用全屏编辑器。原因很现实:权限和关系字段很多,如果用小弹窗会非常拥挤。

全屏编辑器适合这种“单个实体、多块关系”的场景:

  • 左侧或上方是基础信息。
  • 中间是角色、权限、用户组等多选。
  • 保存前可以整体检查。

ScopedChecklist#

ScopedChecklist 用于按应用或资源范围展示可选项。它支持搜索、勾选、显示元信息。

这对用户和用户组的授权尤其重要。管理员不应该面对一大串无结构的 ID,而应该看到权限所属应用和资源分组。

PermissionMatrix#

PermissionMatrix.vue 是权限选择的核心组件。它会把权限整理成树形:

应用
资源
权限

每一层都可以批量选择:

  • 勾选应用:选择该应用下全部权限。
  • 勾选资源:选择该资源下全部权限。
  • 勾选权限:选择单个权限。

组件内部用 selectedSet 做勾选状态计算,用 expanded 维护展开状态。它还会根据 roleClientId 过滤当前角色所属应用,避免把不同应用的权限误分配给某个应用角色。

这种组件比普通多选框更适合权限系统,因为权限本身是层级数据。

个人中心和会话#

普通用户至少需要两个页面:

  • 个人中心。
  • 会话管理。

个人中心提供:

  • 查看用户名、邮箱、手机号、显示名、角色、用户组。
  • 修改邮箱。
  • 修改密码。
  • 查看登录日志。

会话页提供:

  • 当前用户 session 列表。
  • 撤销指定会话。
  • 退出其他设备。

这些功能不只是后台管理补充,也属于 SSO 系统的安全基础能力。用户可以发现异常设备,并主动撤销。

安全策略页#

SecurityView.vue 管理平台级安全配置:

  • 密码长度。
  • 密码是否要求数字。
  • 密码是否要求字母。
  • 验证码阈值。
  • 锁定阈值。
  • 锁定时间。
  • access token TTL。
  • refresh token TTL。
  • authorization code TTL。
  • CORS origin。
  • CORS origin suffix。
  • 签名密钥轮换。

这个页面只开放给平台管理员。签名密钥轮换是高风险操作,前端提供确认动作,后端负责实际轮换。

审计日志页#

审计日志和登录日志是定位问题时最有价值的页面。

登录日志关注:

  • 谁尝试登录。
  • 成功还是失败。
  • 失败原因。
  • IP 和 User-Agent。
  • 时间。

审计日志关注:

  • 谁操作。
  • 操作动作。
  • 目标类型和目标 ID。
  • 结果。
  • 元数据。
  • 时间。

对个人项目来说,审计日志常被忽略。但 SSO 是所有业务系统的入口,一旦账号、权限或 client 被修改,必须能追踪。

错误文案#

utils/errorMessages.js 把后端错误码映射成前端可读文案。这样页面里不需要重复处理每种错误。

同时,Axios 拦截器保留了 errorData,页面仍然能在需要时读取后端原始字段。验证码就是典型例子。

前端设计取舍#

这个前端有几个明确取舍:

  • 不引入大型后台模板,减少维护负担。
  • 用 localStorage 存 token,实现简单,但需要注意 XSS 风险。
  • 用 Pinia 管理登录态,不把所有业务数据放进全局 store。
  • 路由和菜单做权限感知,但不把它当安全边界。
  • 用全屏编辑器承载复杂关系,而不是在表格里硬塞所有操作。
  • 权限选择用树形矩阵,让管理员能按应用和资源理解授权。

其中最值得关注的是 refresh 并发控制。因为后端实现了 refresh token rotation,前端如果不控制并发,很容易因为多个请求同时刷新而误触发 token 复用检测。

可以继续改进的点#

前端后续可以增强几块:

  • 把一次性 secret 展示从 alert 改成更安全的确认弹窗,支持复制按钮。
  • 对高风险操作增加更明确的二次确认,例如输入应用名或用户名。
  • 给权限变更增加差异预览,保存前显示新增和移除项。
  • 给表格增加分页、排序和更强过滤。
  • 对 token 存储做更严格的安全评估,考虑 httpOnly cookie 或 BFF 模式。
  • 为关键页面补充端到端测试,覆盖登录、刷新、用户授权、应用创建。

前端小结#

这个 Vue 管理后台的价值在于把后端复杂能力组织成可操作界面:

  • 登录态、token 刷新和路由守卫保证基础使用体验。
  • 权限菜单和后端范围控制保持一致。
  • 用户、用户组、角色、权限编辑围绕应用范围组织。
  • 权限矩阵把点号权限转换成更适合管理员理解的树。
  • 个人中心、会话、审计和安全策略补齐了 SSO 管理闭环。

下一篇看业务系统如何接入,以及这个项目如何部署和继续演进。

前端分析:Vue 管理后台如何承接 SSO 权限系统
https://march7th.online/blog/posts/0037-前端分析vue-管理后台如何承接-sso-权限系统/
作者
Yiguo
发布于
2026-07-01
许可协议
CC BY-NC-SA 4.0
最后更新于 2026-07-01
所属合集

个人 SSO 权限系统

记录我为个人多项目搭建统一登录和权限管理系统的完整过程,覆盖架构、OAuth、安全闭环、权限模型、接入和部署。

查看完整合集

目录