权限管理

对于一个 Web 应用,权限管理是经常会涉及的一个话题,在介绍权限管理方案之前,我们先来梳理下常见的权限管理包含哪些方面:

  • 路由鉴权:当用户访问某个没有权限的页面时跳转到无权限页面
  • 接口鉴权:当用户通过操作调用没有权限的接口时跳转到无权限页面
  • 操作鉴权:页面中的某些按钮或区块针对无权限的用户直接隐藏
  • 菜单鉴权:某些菜单针对无权限的用户直接隐藏(属于操作鉴权的一种)

权限组件

设计一个权限组件首先要知道当前用户的角色,角色信息通常存在服务端,因此我们需要服务端将当前用户的角色输出到前端,推荐服务端将当前用户角色写在 cookie 中,然后在前端代码中读取角色信息。

接着我们来实现权限组件:

  1. // src/components/Auth/index.jsx
  2. import React from 'react';
  3. import cookie from 'cookie';
  4. // Exception 组件需要业务自己实现
  5. import Exception from '@/components/Exception';
  6. /**
  7. * Auth Component
  8. *
  9. * <Auth authorities={['admin']} hidden={true}>
  10. * <Button />
  11. * </Auth>
  12. *
  13. * @params {array} authorities 允许哪些角色使用
  14. * @params {boolean} hidden 无权限时是否直接隐藏
  15. */
  16. export function Auth({ children, authorities = [], hidden }) {
  17. // 服务端将当前用户角色 authority 保存在 cookie 中,前端负责读取 cookie
  18. const { authority } = cookie.parse(document.cookie);
  19. const hasAuth = authorities.indexOf(authority) !== -1;
  20. if (hasAuth) {
  21. // 有权限直接渲染内容
  22. return children;
  23. } else {
  24. // 无权限
  25. if (hidden) {
  26. // 无权限则不显示内容
  27. return null
  28. } else {
  29. // 无权限则显示指定 UI,也可以跳转到统一的无权限页面
  30. return (
  31. <Exception
  32. statusCode="403"
  33. description="抱歉,你没有权限访问该页面"
  34. />
  35. );
  36. }
  37. }
  38. };
  39. /**
  40. * HOC 方式使用
  41. *
  42. * withAuth({
  43. * authorities: ['admin'],
  44. * })(ListPage);
  45. */
  46. export function withAuth(params) => (WrapperedComponent) => {
  47. return (props) => {
  48. return (
  49. <Auth {...params}>
  50. <WrapperedComponent {...props} />
  51. </Auth>
  52. );
  53. };
  54. };

使用 Auth 组件

路由鉴权

路由鉴权针对页面级组件,直接在组件层面使用上面的 withAuth 控制页面的权限:

  1. // src/pages/List
  2. import React from 'react';
  3. import { withAuth } from '@/components/Auth';
  4. function List() {
  5. return (
  6. <div className="list-page">
  7. <Table />
  8. </div>
  9. );
  10. }
  11. // 仅 admin 角色可访问该页面
  12. export default withAuth({
  13. authorities: ['admin'],
  14. })(List);

接口鉴权

请参考文档前后端通信,业务上封装统一的请求方法,与服务端约定接口协议,前端根据状态码判断无权限、未登录等状态,然后跳转到指定页面。

操作鉴权

如果页面中某个组件要根据角色判断是否显示,可以使用 Auth 组件,同时传入 hidden={true}

  1. <Auth authorities={['admin']} hidden={true}>
  2. <Button>删除</Button>
  3. </Auth>

菜单鉴权

当需要对某些菜单项进行权限控制,只需在对应的菜单项设置 authorities 属性即可,当前用户的权限如果与 authorities 中指定的权限不匹配,菜单项将不显示。

  1. // src/config/menu.js
  2. const headerMenuConfig = [
  3. // ...
  4. {
  5. name: '反馈',
  6. path: 'https://github.com/alibaba/ice',
  7. external: true,
  8. newWindow: true,
  9. icon: 'message',
  10. authorities: ['admin'], // 当前用户权限为 admin 时显示菜单,否则隐藏
  11. },
  12. ];
  13. const asideMenuConfig = [
  14. {
  15. name: '文章管理',
  16. path: '/post',
  17. icon: 'edit',
  18. authorities: ['admin', 'user'], // 当前用户权限为 admin 或者 user 时显示菜单,否则隐藏
  19. children: [
  20. {
  21. name: '文章列表',
  22. path: '/post/list',
  23. authorities: ['user'], // 当前用户权限为 user 时显示菜单,否则隐藏
  24. },
  25. {
  26. name: '添加文章',
  27. path: '/post/create'
  28. },
  29. ],
  30. }
  31. ];

控制菜单项渲染也是使用 Auth 组件实现,当菜单项有配置 authorites 属性时,使用 Auth 组件包裹,权限不匹配时组件隐藏。

  1. /**
  2. * 根据权限决定是否渲染某个表单项
  3. * @param {object} item - 菜单项组件
  4. * @param {array} authorities - 菜单项允许权限数组
  5. * @param {string} key - 当前菜单项的 key
  6. * @return {object} 渲染的菜单项
  7. */
  8. function renderAuthItem(item, authorities, key) {
  9. if (authorities) {
  10. return (
  11. <Auth
  12. authorities={authorities}
  13. hidden
  14. key={key}
  15. >
  16. {item}
  17. </Auth>
  18. );
  19. } else {
  20. return item;
  21. }
  22. }