技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTBR

前端如何来做权限管理?

所属馆藏
Browser
文件格式
Markdown
原始路径
Browser/18-前端如何来做权限管理?
本文目录12 个章节
  1. 一、先区分认证和授权
  2. 二、权限模型:从角色到策略
  3. 三、前端权限通常分为四层
  4. 四、推荐的整体流程
  5. 五、Vue Router 中的路由守卫
  6. 六、菜单和按钮权限
  7. 七、服务端授权的最小示例
  8. 八、Cookie、Token 和 CSRF 的关系
  9. 九、常见错误和改进
  10. 十、权限系统设计清单
  11. 总结
  12. 参考资料

前端如何来做权限管理?

Category(分类): Browser, Security Status: 已更新

原文只保留了一个权限管理资料链接。这里补齐原文想讨论的“角色、页面、菜单、按钮和接口权限”内容,并补充现代 Web 应用中必须明确的边界:前端权限控制主要改善用户体验,真正的授权必须由服务端在每一次受保护请求中执行。

原文资料:前端如何来做权限管理?

一、先区分认证和授权

很多文章把“登录”和“权限”混在一起,但它们解决的是两个问题:

  • 认证(Authentication,AuthN):你是谁?例如账号密码、Passkey、企业 SSO 登录。
  • 授权(Authorization,AuthZ):你可以对哪个资源执行什么操作?例如用户能否编辑某篇文章、管理员能否导出报表。

登录成功只代表服务端识别了主体(subject),不代表主体可以访问所有资源。一次授权判断通常至少包含:

主体 subject + 操作 action + 资源 resource + 上下文 context

例如:

用户 alice + update + 文章 42 + 所属租户 tenant-a

授权结果应该由服务端依据可信数据和策略计算,不能由浏览器传来的 role=admin、隐藏按钮或路由参数决定。

二、权限模型:从角色到策略

1. RBAC:基于角色的访问控制

RBAC(Role-Based Access Control)适合组织结构比较稳定的后台系统:

用户(User) -> 角色(Role) -> 权限(Permission)

alice -> editor -> article:read
                 article:create
                 article:update

一个权限最好是稳定的动作和资源组合,而不是把大量页面 URL 当成权限本身:

article:read
article:update
article:delete
report:export
user:invite

RBAC 的优点是管理简单、容易审计;缺点是角色数量可能随业务条件爆炸。例如“只能编辑自己部门、工作日、指定地区的文章”,仅靠角色很难表达。

2. ABAC:基于属性的访问控制

ABAC(Attribute-Based Access Control)根据主体、资源、动作和环境属性判断:

允许:
  subject.department == resource.department
  && subject.permissions 包含 "article:update"
  && resource.status != "locked"

ABAC 更灵活,但策略设计、调试、审计和性能成本更高。实际项目常采用“RBAC 分配基础权限 + ABAC 处理资源归属和上下文”的组合。

3. 资源关系和租户边界

协作产品还可能使用 ReBAC(Relationship-Based Access Control):

用户 alice 是文档 42 所属项目的 editor

多租户系统必须把 tenantId、组织、项目和资源归属纳入授权判断。只在前端切换租户下拉框、只在 URL 中传 tenantId 都不能构成安全隔离。

三、前端权限通常分为四层

层级前端可以做什么服务端必须做什么
页面/路由未登录跳转登录,未授权展示 403 页面校验页面对应 API 和资源权限
菜单根据权限隐藏或禁用导航项不因菜单不可见就放开接口
按钮/操作隐藏“删除”“导出”等操作,减少误操作再次校验具体动作
数据/字段隐藏不应显示的列和敏感字段查询时过滤字段,禁止越权读取

隐藏按钮不是安全措施。 用户可以修改 JavaScript、直接调用 API、重放请求或绕过前端路由。OWASP 将访问控制列为服务端必须执行的安全职责,前端检查只能作为 UX 层的提前提示。

四、推荐的整体流程

  1. 用户通过登录、Passkey、企业 SSO 或 OAuth/OIDC 完成认证;
  2. 服务端创建会话或签发符合协议的令牌;
  3. 浏览器访问 /api/me,获取当前用户的非敏感资料、租户和展示用权限摘要;
  4. 前端据此生成菜单、路由提示和按钮状态;
  5. 每个业务 API 在服务端重新加载主体、资源和策略并做授权;
  6. 权限变更、登出、封禁或会话失效后,服务端立即拒绝后续请求;
  7. 前端遇到 401 重新认证,遇到 403 展示无权限,而不是无限重试。

示例响应可以是:

{
  "user": {
    "id": "u_42",
    "displayName": "Ada",
    "tenantId": "tenant-a"
  },
  "permissions": [
    "article:read",
    "article:update"
  ],
  "policyVersion": 12
}

权限列表只用于前端展示,不应包含密码、私钥、完整会话令牌或“前端相信即可”的管理员标志。复杂资源权限应由服务端通过 API 返回可执行能力,或在业务接口中直接判断。

五、Vue Router 中的路由守卫

路由守卫可以改善导航体验,但不能替代 API 授权:

const routes = [
  {
    path: '/articles',
    component: () => import('./pages/Articles.vue'),
    meta: { requiresAuth: true, permission: 'article:read' }
  },
  {
    path: '/articles/new',
    component: () => import('./pages/ArticleEditor.vue'),
    meta: { requiresAuth: true, permission: 'article:create' }
  }
]

router.beforeEach(async to => {
  const auth = useAuthStore()

  if (to.meta.requiresAuth && !auth.isAuthenticated) {
    return {
      name: 'login',
      query: { redirect: to.fullPath }
    }
  }

  if (to.meta.permission && !auth.can(to.meta.permission)) {
    return { name: 'forbidden' }
  }
})

注意事项:

  • 路由 meta 是客户端代码,不能存放秘密,也不能作为后端权限配置的唯一来源;
  • 认证状态初始化完成前,不要因为默认值 false 把已登录用户误导向登录页;
  • redirect 参数必须限制为站内路径,避免开放重定向;
  • 懒加载组件失败、会话过期和权限变更都要有明确的错误状态;
  • 如果权限变更后仍在旧页面,应让下一次 API 请求返回 403,并刷新权限摘要。

六、菜单和按钮权限

1. 组件函数

<script setup lang="ts">
const auth = useAuthStore()
</script>

<template>
  <button
    v-if="auth.can('article:delete')"
    type="button"
    @click="deleteArticle"
  >
    删除文章
  </button>
</template>

更复杂的场景可以封装 Can 组件或 v-permission 指令,但它们本质上都是展示层逻辑。不要因为按钮被 v-if 移除,就认为接口安全。

2. 动态菜单和路由

如果菜单由服务端返回,前端仍要:

  • 校验返回数据的结构,避免把任意字符串当作组件名或 HTML 执行;
  • 使用本地允许的路由映射,不根据服务端字符串动态执行任意代码;
  • 对无法匹配的路径显示 404,而不是任意 import()
  • 在服务器端继续校验每个菜单所代表的业务 API。
const routeComponents = {
  articles: () => import('./pages/Articles.vue'),
  reports: () => import('./pages/Reports.vue')
} as const

for (const item of menuFromServer) {
  const component = routeComponents[item.key as keyof typeof routeComponents]
  if (!component) continue

  router.addRoute({
    name: item.key,
    path: item.path,
    component,
    meta: { permission: item.permission }
  })
}

动态注册路由要处理重复注册、刷新恢复、登出清理和权限变化。它解决的是导航组织问题,不是授权问题。

七、服务端授权的最小示例

下面是 Express 风格的示意代码。真实项目应使用成熟的会话中间件、数据库事务、策略引擎和集中日志:

const requirePermission = permission => async (req, res, next) => {
  if (!req.user) {
    return res.status(401).json({ code: 'UNAUTHENTICATED' })
  }

  const allowed = await policy.can({
    subject: req.user,
    action: permission,
    resource: req.resource
  })

  if (!allowed) {
    return res.status(403).json({ code: 'FORBIDDEN' })
  }

  return next()
}

app.patch(
  '/api/articles/:id',
  requireAuthenticated,
  loadArticle,
  requirePermission('article:update'),
  updateArticle
)

状态码语义通常是:

  • 401 Unauthorized:当前请求没有通过认证,通常没有有效会话或令牌;
  • 403 Forbidden:服务器识别了主体,但该主体没有执行操作的权限;
  • 404 Not Found:某些资源系统会对无权资源故意返回 404,避免泄露资源是否存在;这应作为整体信息泄露策略设计,而不是机械替换所有 403。

最容易被忽略的是对象级越权(IDOR/BOLA):

// 错误:只根据 URL id 查询并返回
const unsafeArticle = await db.article.findById(req.params.id)

// 正确思路:查询和租户/主体约束一起执行
const article = await db.article.findFirst({
  where: {
    id: req.params.id,
    tenantId: req.user.tenantId,
    // 或通过策略确认 req.user 对该资源有 read/update 权限
  }
})

不要先查询全部数据再在浏览器过滤;敏感字段和不属于当前租户的数据根本不应出现在响应中。

八、Cookie、Token 和 CSRF 的关系

权限管理经常和登录凭证一起讨论,但凭证放在哪里不是授权模型本身:

  • 服务端会话常把不可猜的 session ID 放在 HttpOnly; Secure; SameSite=Lax/Strict Cookie 中;
  • Cookie 会自动随符合规则的请求发送,因此需要 CSRF 防护;
  • 把访问令牌放在 localStorage 可以避免浏览器自动发送,但 XSS 能读取它,不能因此称为“绝对安全”;
  • HttpOnly 也不能消除 XSS,攻击脚本仍可能借助当前页面发起已认证操作;
  • 跨站请求、CORS、SameSite 和 CSRF Token 应按具体部署组合设计。

前端权限控制不能靠“把 Token 放到更隐蔽的变量”解决。凭证生命周期、撤销、轮换、权限变更和异常会话都需要服务端策略。

九、常见错误和改进

错误 1:只做前端路由拦截

攻击者可以直接请求 /api/admin/users。改进:每个服务端接口都做认证、资源加载和授权,并默认拒绝。

错误 2:相信前端传来的角色

role=adminisAdmin=true、前端 localStorage 中的权限都可被修改。改进:角色和权限来自服务端可信存储,服务端从会话或令牌确认主体。

错误 3:把菜单当作权限系统

菜单只是导航,不应决定数据库查询范围。改进:资源归属、动作和租户约束放进服务端策略。

错误 4:权限变更只刷新浏览器

用户被撤销权限后,旧页面可能仍显示按钮。改进:服务端立即拒绝;前端处理 401/403,并使权限摘要失效。

错误 5:把“加密前端权限配置”当成保护

浏览器必须拿到用于展示的代码和数据,用户可以查看或调试它。加密配置无法替代服务端授权;真正的秘密不应下发到浏览器。

错误 6:忽略审计和高风险操作

删除、导出、转账、修改权限等操作应记录主体、资源、结果、请求 ID、时间和必要的风险信号;高风险操作可要求重新认证、二次确认或 WebAuthn。

十、权限系统设计清单

  • 默认拒绝,采用最小权限原则;
  • 认证、授权、资源归属和租户隔离分开设计;
  • 前端检查只服务于 UX,服务端检查才是安全边界;
  • 使用稳定的权限命名和策略版本,避免散落字符串;
  • 对角色、权限、资源和策略变更建立审计记录;
  • 防范对象级越权、批量赋值、路径穿越、开放重定向和 CSRF;
  • 不在前端暴露密钥、私钥、数据库凭据或完整敏感用户资料;
  • 会话过期、撤销、登出、并发登录和权限变更有明确行为;
  • 401403 做不同处理,不要无限重试;
  • 用自动化授权测试覆盖允许、拒绝、跨租户和资源所有权场景;
  • 对高风险操作实施重新认证、二次审批或不可抵赖的审计机制。

总结

  1. 认证回答“你是谁”,授权回答“你能对哪个资源做什么”;
  2. RBAC 适合稳定的角色体系,ABAC/ReBAC 适合资源归属和上下文更复杂的业务;
  3. 页面、菜单、按钮和前端路由都可以做体验层提示,但不能作为安全边界;
  4. 服务端必须在每个受保护请求中验证主体、动作、资源和租户;
  5. Cookie、Token、SSO 和 OAuth 是身份凭证或认证协议,不会自动替代授权策略;
  6. 默认拒绝、最小权限、对象级授权、审计和撤销能力是可维护权限系统的基础。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS