权限与访问控制

目录

一、概念

"用户权限与访问控制类业务"是指在用户身份已确认的前提下,通过角色、权限、规则和策略,决定用户:

  • 能不能访问
  • 能不能操作
  • 能看到什么

"用户权限与访问控制"在系统中的位置(非常重要):

typescript 复制代码
用户身份认证
(你是谁)
    ↓
用户权限与访问控制
(你能不能)
    ↓
业务功能执行

权限系统是身份认证与业务执行之间的"闸门"。

"用户权限与访问控制类业务"在前端主要体现为:

  • 登录态管理
  • 路由访问控制
  • 动态菜单
  • 操作级权限控制。

前端负责:权限的展示与引导

  • 前端权限系统的目标不是保证安全,而是降低误操作、减少越权尝试、提升用户体验。

后端负责:权限的裁决与执行

  • 真正的安全控制必须由后端统一裁决,前后端形成权限的双层校验体系。

二、权限与访问控制的「能力全景图」

从前端视角,把这个业务域拆成 8 大核心能力:

能力 解决的问题 前端是否参与
身份状态管理 是否登录
角色模型 用户是什么身份
权限点模型 能做哪些操作
路由访问控制 能进哪些页面
动态菜单 能看到哪些入口
按钮级权限 能点哪些操作
黑白名单 是否强制禁止/放行 ⚠️(展示)
接口级校验 是否真正允许 ❌(后端)

📌 前端主要负责:入口、展示、引导、体验控制

三、前端视角的「权限控制分层模型」(核心)

前端权限不是一层,而是四层:

  • 登录态层
  • 路由层
  • 菜单层
  • 组件 / 操作层

1、登录态层(Authentication State)

前端职责:

  • 判断是否已登录
  • 维护 Token / Session
  • 处理登录失效

典型实现:

  • 路由守卫
  • 请求拦截器
  • Token 过期重定向

📌 这是所有权限的前提条件

2、路由层(Page Access Control)

问题:用户能否访问某个页面?

实现方式:

  • 路由 meta.permission
  • 登录后动态注册路由
  • 未授权跳转 403

📌 防止"手输 URL 访问"

3、菜单层(Navigation Control)

问题:用户能看到哪些功能入口?

实现方式:

  • 后端返回菜单树
  • 或前端根据权限过滤菜单
  • 菜单 ≠ 路由(重要)

📌 菜单是权限的 UI 映射

4、组件 / 操作层(Action Control)

问题:用户能不能执行具体操作?

实现方式:

  • v-if / 权限指令
  • hooks / 高阶组件
  • 操作前二次校验

📌 按钮权限永远不能当安全机制

四、前端权限系统的"典型数据流"

typescript 复制代码
登录成功
↓
获取用户信息(角色 / 权限 / 菜单 / 名单状态)
↓
初始化权限上下文(Store)
↓
动态生成路由
↓
渲染菜单
↓
控制组件 / 操作

📌 这是90% 企业级系统的真实流程

五、权限模型的三种常见设计(前端必须懂)

  • 基于角色的 RBAC
  • 基于权限点的 PBAC
  • 基于属性/规则的 ABAC

1、RBAC(基于角色)

typescript 复制代码
用户 → 角色 → 权限

前端特点:

  • 好理解
  • 菜单生成简单
  • 扩展性一般

2、PBAC(基于权限点)

typescript 复制代码
用户 → 权限点

前端特点:

  • 按钮级控制友好
  • 菜单/操作粒度细
  • 前端维护成本较高

3、ABAC(基于属性/规则)

typescript 复制代码
用户属性 + 资源属性 + 环境属性

前端特点:

  • 前端只做展示
  • 复杂规则交给后端
  • 常见于金融风控

📌 金融系统:RBAC + ABAC 混合

六、重点「权限与访问控制」业务剖析

1、用户鉴权

没有可靠的用户鉴权,后面的权限、菜单、风控全部没有意义。

用户鉴权是整个系统安全的第一道防线:

  • 前端负责凭证的安全存储、携带和失效处理。
  • 后端负责身份真实性校验和凭证签发。

在企业和金融系统中通常采用双 Token 机制以兼顾安全性和用户体验。

(1)、用户鉴权概念

用户鉴权,是用于确认"当前发起请求的主体是谁,并且是否真实可信"的过程。

关键词只有一个:

  • 你是谁

鉴权 ≠ 权限(必须先区分):

项目 用户鉴权 用户权限
核心问题 你是谁 你能做什么
是否必须 ❌(视业务)
是否安全核心 ⚠️
典型产物 Token / Session 菜单 / 按钮 / 规则

📌 鉴权是所有安全体系的地基

(2)、用户鉴权的「标准模型」(企业级通用)

抽象成 4 个核心步骤:

  • 身份声明(Credentials)
  • 身份验证(Verify)
  • 身份凭证发放(Token / Session)
  • 身份凭证校验(Request Auth)

各步骤的职责分工(前端视角):

阶段 前端职责 后端职责
登录 收集凭证 验证真实性
发证 存储凭证 生成 Token
使用 携带 Token 校验 Token
失效 处理过期 判定失效

📌 前端 不验证身份真实性,只负责携带与管理凭证

(3)、主流鉴权方案全景对比(你必须会)

方案 是否企业常用 特点
Cookie + Session 简单、CSRF 风险
JWT(Bearer Token) 无状态、最主流
OAuth2 / SSO 企业/平台级
双 Token(Access + Refresh) 金融级
证书 / Key ⚠️ 系统对系统

(4)、JWT 鉴权机制(前端必须吃透)

①、JWT 本质
typescript 复制代码
JWT = Header + Payload + Signature
  • Payload:用户身份声明(userId / role / exp)
  • Signature:防篡改

📌 JWT ≠ 加密,只是签名

②、企业级 JWT 设计(重点)

❌ 错误做法

  • JWT 放 localStorage
  • 永久有效
  • 前端完全信任 JWT

✅ 正确做法(金融级)

  • Access Token:短期(5~30min)
  • Refresh Token:长期(7~30d)
  • Refresh Token 存 HttpOnly Cookie
  • Access Token 仅用于请求

(5)、前端如何"正确"做用户鉴权(分层讲)

①、登录阶段(Login)

前端:

  • 收集账号 / 密码 / 验证码
  • HTTPS 发送

后端:

  • 验证身份
  • 返回 Token
typescript 复制代码
{
  "accessToken": "xxx",
  "expiresIn": 1800,
  "user": {
    "id": 1,
    "name": "Alice"
  }
}
②、凭证存储(非常关键)

企业级推荐方案:

凭证 存储位置
Access Token 内存 / Store
Refresh Token HttpOnly Cookie

📌 防 XSS + 防 CSRF 的平衡方案

③、请求鉴权(Request Authentication)

前端统一拦截器:

typescript 复制代码
axios.interceptors.request.use(config => {
  config.headers.Authorization = `Bearer ${accessToken}`
  return config
})
④、过期处理(企业级重点)
typescript 复制代码
接口返回 401
↓
使用 Refresh Token
↓
刷新 Access Token
↓
重放原请求

📌 用户无感刷新登录态

(6)、一个「简单但完整」的企业级典例

下面是一个典型后台管理系统的鉴权方案。

技术栈:

前端:React / Vue SPA

后端:REST API

鉴权:JWT + 双 Token

场景:金融后台

①、登录流程
typescript 复制代码
/login
↓
校验账号密码
↓
返回 accessToken
↓
refreshToken 写入 HttpOnly Cookie
②、前端核心代码(示意)

登录成功处理:

typescript 复制代码
store.setAccessToken(token)
store.setUser(userInfo)

请求拦截器:

typescript 复制代码
axios.interceptors.response.use(
  res => res,
  async err => {
    if (err.response.status === 401) {
      await refreshToken()
      return axios(err.config)
    }
    return Promise.reject(err)
  }
)

路由守卫(鉴权入口):

typescript 复制代码
if (!accessToken && to.path !== '/login') {
  redirect('/login')
}
③、后端核心原则(前端要懂)

所有接口校验 Token:

  • Token 校验独立于菜单 / 权限
  • Token 失效立即拒绝请求

📌 鉴权是"所有接口"的第一道门

2、黑白名单

黑白名单并不属于权限模型,而是一种高优先级的访问控制与风控机制。

在系统中,它通常位于身份认证和权限校验之后,

前端仅负责展示与引导,最终是否允许访问由后端统一裁决。

权限决定"正常能不能做",黑白名单决定"特殊情况下能不能做"。

(1)、黑白名单概念

黑白名单是一种"强制访问控制与风控兜底机制",

用于在身份与权限校验通过之后,对特定主体进行"额外禁止或额外放行"。

📌 核心关键词:

强制 / 兜底 / 高优先级

黑白名单解决的不是"权限问题":

问题 是否由黑白名单解决
你是谁
你有什么权限
是否强制禁止
是否特权放行
是否临时控制

📌 黑白名单永远高于角色、权限、菜单

(2)、黑白名单在系统中的"正确位置"

标准安全顺序(金融系统):

  • 身份认证(Authentication)
  • 权限校验(Authorization)
  • 黑白名单 / 风控校验(Access Control)
  • 业务执行

黑白名单不是前置认证,而是"最终闸门"

(3)、黑白名单的业务分类(前端必须理解)

①、按"对象"分类
对象 示例
用户 封禁账号、风控用户
角色 禁止某类用户操作
IP 风控 IP
设备 异常设备
接口 临时封禁能力

📌 前端通常只感知"用户级 / 功能级"

②、按"作用方式"分类

黑名单(Block):

  • 强制禁止
  • 无视权限
  • 常用于风控

白名单(Allow):

  • 强制放行
  • 可绕过部分限制
  • 常用于内部 / 灰度

(4)、前端在黑白名单中的真实职责(重点)

⚠️ 非常关键的一点:

  • 前端不是黑白名单的裁决者,只是"感知者"和"执行者(展示层)"。

前端能做什么?

能力 是否允许
隐藏入口
禁用按钮
操作前提示
阻断请求 ⚠️(体验层)
决定是否安全

🚫 前端不能做什么?

  • 决定是否真正允许
  • 作为唯一拦截手段
  • 依赖本地逻辑保证安全

(5)、企业级黑白名单的"数据模型"(你要会看)

后端返回的典型结构:

typescript 复制代码
{
  "userId": 1001,
  "blacklist": {
    "global": false,
    "actions": ["withdraw", "transfer"],
    "reason": "风控冻结"
  },
  "whitelist": {
    "actions": ["internal-debug"]
  }
}

📌 特点:

  • 不是简单 true / false
  • 有作用范围
  • 有原因
  • 可动态调整

(6)、前端如何"正确"使用黑白名单(分层讲)

①、初始化阶段(登录后)
typescript 复制代码
登录成功
↓
获取用户画像(含名单状态)
↓
写入权限上下文(Store)
②、菜单 / 页面层控制
typescript 复制代码
if (blacklist.actions.includes('withdraw')) {
  hideMenu('withdraw')
}

📌 只做展示控制

③、操作前校验(体验级)
typescript 复制代码
function beforeAction(action) {
  if (blacklist.actions.includes(action)) {
    showToast('当前账号已被限制操作')
    return false
  }
  return true
}

📌 防止无意义请求 + 明确提示用户

④、接口异常兜底(关键)

即使前端没拦住:

typescript 复制代码
接口请求
↓
后端黑名单校验
↓
返回 403 / 451
↓
前端统一提示

📌 这是最终安全兜底

(7)、一个「简单但企业级」的典例

场景说明:金融后台系统

功能:提现

风控:部分用户被临时冻结提现

①、后端规则(你要懂)
typescript 复制代码
if (user in withdraw_blacklist) {
  reject request
}
②、前端权限上下文(示意)
typescript 复制代码
const authContext = {
  permissions: ['withdraw'],
  blacklist: ['withdraw']
}
③、前端按钮控制
html 复制代码
<Button
  disabled={blacklist.includes('withdraw')}
  onClick={handleWithdraw}
>
  提现
</Button>
④、操作前提示
typescript 复制代码
function handleWithdraw() {
  if (blacklist.includes('withdraw')) {
    alert('账号风控中,暂不可提现')
    return
  }
  submitWithdraw()
}
⑤、接口兜底处理
typescript 复制代码
axios.post('/withdraw').catch(err => {
  if (err.status === 403) {
    alert('操作被系统拦截')
  }
})

📌 前后端双层拦截

3、动态菜单

动态菜单本质上是权限与访问控制结果在前端导航层的映射,

它的目标是降低误操作风险、提升可用性与合规性,

但并不参与最终的安全裁决,真正的权限校验必须由后端完成。

动态菜单不是"你能不能做",而是"你该不该看到"。

(1)、动态菜单的概念

动态菜单,是"用户权限与访问控制结果在前端的导航层映射",用于决定"当前用户能看到哪些功能入口"。

📌 关键词:

权限结果 / 导航层 / 展示与引导

🚫 动态菜单不解决什么?

  • 不解决身份认证
  • 不解决真正的安全校验
  • 不防止接口越权

📌 它不是安全机制,而是"权限的 UI 表现"

(2)、动态菜单在系统中的"正确位置"

在企业 / 金融系统中,完整链路是:

typescript 复制代码
身份认证(你是谁)
↓
权限 / 黑白名单(你能不能)
↓
动态菜单(你能看到什么)
↓
路由访问
↓
具体操作

菜单一定发生在"权限已确定之后"

(3)、为什么企业系统一定要用动态菜单?

业务复杂度决定的:

  • 不同角色看到完全不同系统
  • 功能频繁开关 / 灰度
  • 多租户 / 多业务线

📌 静态菜单根本不可维护

金融系统的现实需求:

  • 降低误操作风险
  • 减少越权尝试
  • 强化合规性("你不该看到的就不要看到")

(4)、动态菜单的三种主流设计模式(前端必须懂)

①、后端全量下发菜单(最常见)
typescript 复制代码
后端根据权限
↓
直接返回可用菜单树
↓
前端直接渲染

特点:

  • 前端最简单
  • 权限集中在后端
  • 金融系统最常用

📌 推荐指数:⭐⭐⭐⭐⭐

②、前端根据权限过滤菜单(次常见)
typescript 复制代码
后端返回权限点
↓
前端本地菜单表
↓
根据权限过滤

特点:

  • 前端更灵活
  • 维护成本高
  • 易出错

📌 适合中后台、内部系统

③、混合模式(企业级常见)
typescript 复制代码
后端返回菜单骨架 + 权限
↓
前端补充结构 / 本地能力

📌 大型系统 / 微前端常用

(5)、动态菜单的"标准数据模型"

企业级菜单一定是树结构 + 元数据:

typescript 复制代码
[
  {
    "key": "fund",
    "title": "资金管理",
    "icon": "money",
    "path": "/fund",
    "children": [
      {
        "key": "withdraw",
        "title": "提现",
        "path": "/fund/withdraw",
        "permission": "withdraw"
      }
    ]
  }
]

核心字段说明:

字段 作用
key 唯一标识
title 菜单名
path 路由路径
permission 所需权限
children 子菜单

📌 菜单 ≠ 路由,但通常一一对应

(6)、前端做动态菜单的"标准步骤"

①、登录后初始化权限上下文
typescript 复制代码
登录成功
↓
获取用户信息(角色 / 权限 / 菜单)
↓
存入 Store
②、根据权限生成菜单树
typescript 复制代码
function filterMenu(menu, permissions) {
  return menu
    .filter(item =>
      !item.permission || permissions.includes(item.permission)
    )
    .map(item => ({
      ...item,
      children: item.children
        ? filterMenu(item.children, permissions)
        : []
    }))
}

📌 纯函数、可测试

③、渲染菜单(UI 框架无关
html 复制代码
<Menu>
  {menu.map(item => (
    <MenuItem key={item.key} to={item.path}>
      {item.title}
    </MenuItem>
  ))}
</Menu>
④、路由层兜底(非常关键)

即使菜单隐藏了:

typescript 复制代码
用户手输 URL
↓
路由守卫校验权限
↓
无权限 → 403

📌 菜单永远不能当路由安全

(7)、一个「简单但企业级」的动态菜单典例

场景说明:金融后台

角色:普通用户 / 管理员

功能:查看账户 / 提现 / 审批

①、后端返回数据(示意)
typescript 复制代码
{
  "permissions": ["view", "withdraw"],
  "menu": [
    {
      "key": "account",
      "title": "账户",
      "path": "/account"
    },
    {
      "key": "withdraw",
      "title": "提现",
      "path": "/withdraw",
      "permission": "withdraw"
    },
    {
      "key": "approve",
      "title": "审批",
      "path": "/approve",
      "permission": "approve"
    }
  ]
}
②、前端生成菜单
typescript 复制代码
const menu = filterMenu(menuFromServer, permissions)

结果只剩:

  • 账户
  • 提现
③、路由守卫兜底
typescript 复制代码
if (!hasPermission(route.permission)) {
  redirect('/403')
}

📌 企业级必备

相关推荐
SuperherRo6 个月前
Web攻防-文件上传&黑白名单&MIME&JS前端&执行权限&编码解析&OSS存储&分域名&应用场景
文件上传·mime·黑白名单·js前端·执行权限·编码解析·oss存储
sen_shan9 个月前
Vue3+Vite+TypeScript+Element Plus开发-10.多用户动态加载菜单
vue.js·typescript·vue3·element·element plus·动态菜单·多用户动态加载菜单
arbboter9 个月前
【AI插件开发】Notepad++ AI插件开发实践:从Dock窗口集成到功能菜单实现
人工智能·notepad++·动态菜单·notepad++插件开发·dock窗口集成·ai代码辅助工具·ai对话窗口
山川湖海2 年前
React动态菜单权限控制完全指南
react.js·动态菜单·react权限菜单
会做梦的辣条鱼2 年前
vue+iView 动态侧边栏菜单保持高亮选中
前端·vue.js·iview·view design·menu·sider·动态菜单