揭阳市文章资讯

前端权限之接口权限与接口拦截器示例详解

2026-05-31 17:51:01 浏览次数:0
详细信息

前端权限之接口权限与拦截器示例详解

本文将深入讲解前端权限控制中的接口权限管理,并提供完整的接口拦截器实现示例。

一、接口权限的核心概念

1.1 什么是接口权限

接口权限是指前端应用对后端API接口的访问控制,确保用户只能访问其有权限的接口资源。

1.2 接口权限与页面权限的区别

二、接口权限实现方案

2.1 常用实现方式

基于角色的权限控制(RBAC) 基于权限码的权限控制 动态路由/接口权限

三、Axios 拦截器实现示例

3.1 基础拦截器配置

// axios-instance.js
import axios from 'axios';
import { message } from 'antd';
import router from './router';

// 创建axios实例
const instance = axios.create({
  baseURL: process.env.REACT_APP_API_BASE_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// 请求拦截器
instance.interceptors.request.use(
  (config) => {
    // 从本地存储获取token
    const token = localStorage.getItem('access_token');

    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    // 添加请求时间戳
    config.headers['X-Timestamp'] = Date.now();

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
instance.interceptors.response.use(
  (response) => {
    // 处理成功响应
    return response.data;
  },
  (error) => {
    // 处理错误响应
    return handleResponseError(error);
  }
);

// 错误处理函数
const handleResponseError = (error) => {
  if (!error.response) {
    // 网络错误
    message.error('网络连接失败,请检查网络设置');
    return Promise.reject(error);
  }

  const { status, data } = error.response;

  switch (status) {
    case 401:
      // 未授权,跳转到登录页
      handleUnauthorized();
      break;

    case 403:
      // 权限不足
      handleForbidden();
      break;

    case 404:
      message.error('请求的资源不存在');
      break;

    case 500:
      message.error('服务器内部错误');
      break;

    default:
      message.error(data.message || '请求失败');
  }

  return Promise.reject(error);
};

// 处理未授权
const handleUnauthorized = () => {
  localStorage.removeItem('access_token');
  localStorage.removeItem('user_info');
  message.error('登录已过期,请重新登录');
  router.navigate('/login');
};

// 处理禁止访问
const handleForbidden = () => {
  message.error('您没有权限执行此操作');
  // 可以跳转到无权限页面
  // router.navigate('/403');
};

export default instance;

3.2 权限验证拦截器

// permission-interceptor.js
import axios from './axios-instance';
import store from './store';
import { getPermissionCodes } from './utils/permission';

// 需要权限验证的接口映射
const permissionMap = {
  '/api/users': ['user:read', 'user:write'],
  '/api/roles': ['role:manage'],
  '/api/admin': ['admin:all'],
};

// 权限验证请求拦截器
axios.interceptors.request.use(
  (config) => {
    // 检查当前请求是否需要权限验证
    const requiresPermission = checkPermissionRequired(config.url, config.method);

    if (requiresPermission) {
      // 获取用户权限列表
      const userPermissions = getCurrentUserPermissions();

      // 验证权限
      const hasPermission = verifyPermission(
        config.url, 
        config.method, 
        userPermissions
      );

      if (!hasPermission) {
        // 如果没有权限,直接抛出错误
        return Promise.reject({
          response: {
            status: 403,
            data: { message: '没有访问权限' }
          }
        });
      }
    }

    return config;
  },
  (error) => Promise.reject(error)
);

// 检查接口是否需要权限验证
function checkPermissionRequired(url, method) {
  // 排除白名单接口(如登录、注册等公共接口)
  const whiteList = [
    '/api/auth/login',
    '/api/auth/register',
    '/api/public',
  ];

  if (whiteList.some(whiteUrl => url.includes(whiteUrl))) {
    return false;
  }

  // 检查权限映射表
  for (const [apiPath, permissions] of Object.entries(permissionMap)) {
    if (url.includes(apiPath)) {
      return true;
    }
  }

  return false;
}

// 获取当前用户权限
function getCurrentUserPermissions() {
  // 从Redux store或本地存储获取
  const user = store.getState().user;
  if (user && user.permissions) {
    return user.permissions;
  }

  // 或从本地存储获取
  const userInfo = JSON.parse(localStorage.getItem('user_info') || '{}');
  return userInfo.permissions || [];
}

// 验证权限
function verifyPermission(url, method, userPermissions) {
  // 查找对应的权限码
  for (const [apiPath, requiredPermissions] of Object.entries(permissionMap)) {
    if (url.includes(apiPath)) {
      // 检查用户是否拥有所需权限
      const hasAllPermissions = requiredPermissions.every(permission =>
        userPermissions.includes(permission)
      );

      return hasAllPermissions;
    }
  }

  // 如果没有在权限映射表中找到,默认需要验证
  return userPermissions.length > 0;
}

export default axios;

四、完整的权限管理系统示例

4.1 权限管理工具类

// utils/permission.js
class PermissionManager {
  constructor() {
    this.permissionCache = new Map();
  }

  // 初始化权限
  initPermissions(permissions) {
    localStorage.setItem('user_permissions', JSON.stringify(permissions));
    this.permissionCache.clear();
  }

  // 获取权限列表
  getPermissions() {
    const cached = this.permissionCache.get('all');
    if (cached) return cached;

    const permissions = JSON.parse(localStorage.getItem('user_permissions') || '[]');
    this.permissionCache.set('all', permissions);
    return permissions;
  }

  // 检查是否有某个权限
  hasPermission(permissionCode) {
    const permissions = this.getPermissions();
    return permissions.includes(permissionCode);
  }

  // 检查是否有任意一个权限
  hasAnyPermission(permissionCodes) {
    const permissions = this.getPermissions();
    return permissionCodes.some(code => permissions.includes(code));
  }

  // 检查是否有所有权限
  hasAllPermissions(permissionCodes) {
    const permissions = this.getPermissions();
    return permissionCodes.every(code => permissions.includes(code));
  }

  // 清除权限
  clearPermissions() {
    localStorage.removeItem('user_permissions');
    this.permissionCache.clear();
  }
}

// 权限码常量
export const PERMISSIONS = {
  USER_READ: 'user:read',
  USER_WRITE: 'user:write',
  USER_DELETE: 'user:delete',
  ROLE_MANAGE: 'role:manage',
  ADMIN_ALL: 'admin:all',
};

export const permissionManager = new PermissionManager();

4.2 权限高阶组件

// components/withPermission.jsx
import React from 'react';
import { Navigate } from 'react-router-dom';
import { permissionManager } from '../utils/permission';

/**
 * 权限高阶组件
 * @param {Array|string} requiredPermissions 需要的权限
 * @param {ReactNode} fallback 没有权限时显示的内容
 */
const withPermission = (requiredPermissions, fallback = null) => (WrappedComponent) => {
  const PermissionWrapper = (props) => {
    // 转换权限参数为数组
    const permissions = Array.isArray(requiredPermissions)
      ? requiredPermissions
      : [requiredPermissions];

    // 检查权限
    const hasPermission = permissionManager.hasAllPermissions(permissions);

    if (!hasPermission) {
      if (fallback) {
        return fallback;
      }

      // 默认跳转到403页面
      return <Navigate to="/403" replace />;
    }

    return <WrappedComponent {...props} />;
  };

  PermissionWrapper.displayName = `WithPermission(${
    WrappedComponent.displayName || WrappedComponent.name || 'Component'
  })`;

  return PermissionWrapper;
};

export default withPermission;

4.3 使用示例

// App.jsx
import React, { useEffect } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { ConfigProvider, message } from 'antd';
import axios from './api/axios-instance';
import { permissionManager, PERMISSIONS } from './utils/permission';
import withPermission from './components/withPermission';

// 页面组件
import Login from './pages/Login';
import Dashboard from './pages/Dashboard';
import UserManagement from './pages/UserManagement';
import AdminPanel from './pages/AdminPanel';
import NoPermission from './pages/403';

// 带权限的用户管理页面
const UserManagementWithPermission = withPermission(
  [PERMISSIONS.USER_READ, PERMISSIONS.USER_WRITE]
)(UserManagement);

// 带权限的管理员面板
const AdminPanelWithPermission = withPermission(
  PERMISSIONS.ADMIN_ALL
)(AdminPanel);

function App() {
  useEffect(() => {
    // 检查登录状态
    const token = localStorage.getItem('access_token');
    if (token) {
      // 获取用户权限
      fetchUserPermissions();
    }
  }, []);

  const fetchUserPermissions = async () => {
    try {
      const response = await axios.get('/api/user/permissions');
      permissionManager.initPermissions(response.data);
    } catch (error) {
      console.error('获取权限失败:', error);
    }
  };

  return (
    <ConfigProvider>
      <BrowserRouter>
        <Routes>
          <Route path="/login" element={<Login />} />
          <Route path="/" element={<Dashboard />} />
          <Route path="/users" element={<UserManagementWithPermission />} />
          <Route path="/admin" element={<AdminPanelWithPermission />} />
          <Route path="/403" element={<NoPermission />} />
          <Route path="*" element={<Navigate to="/" replace />} />
        </Routes>
      </BrowserRouter>
    </ConfigProvider>
  );
}

export default App;

五、动态接口权限控制

5.1 后端返回接口权限配置

// 后端返回的权限数据结构
const backendPermissions = {
  apiPermissions: {
    '/api/users': {
      GET: ['user:read'],
      POST: ['user:write'],
      PUT: ['user:write'],
      DELETE: ['user:delete']
    },
    '/api/roles': {
      GET: ['role:read'],
      POST: ['role:write'],
      DELETE: ['role:delete']
    }
  },
  userPermissions: ['user:read', 'user:write']
};

// 动态权限拦截器
axios.interceptors.request.use(
  async (config) => {
    // 获取动态权限配置
    const apiPermissions = JSON.parse(
      localStorage.getItem('api_permissions') || '{}'
    );

    const url = config.url;
    const method = config.method.toUpperCase();

    // 查找对应的权限要求
    let requiredPermissions = [];

    for (const [apiPath, methods] of Object.entries(apiPermissions)) {
      if (url.includes(apiPath) && methods[method]) {
        requiredPermissions = methods[method];
        break;
      }
    }

    if (requiredPermissions.length > 0) {
      const userPermissions = permissionManager.getPermissions();
      const hasPermission = requiredPermissions.every(perm => 
        userPermissions.includes(perm)
      );

      if (!hasPermission) {
        return Promise.reject(new Error('权限不足'));
      }
    }

    return config;
  }
);

六、最佳实践和建议

6.1 安全建议

永远不要依赖前端权限验证:前端权限控制只是用户体验优化,真正的安全验证必须在后端进行 定期刷新权限:用户权限变更后及时更新前端权限信息 错误信息模糊化:权限错误时不要暴露过多系统信息

6.2 性能优化

权限缓存:合理使用缓存减少权限验证开销 懒加载权限:按需加载权限配置 权限分组:将相关权限分组管理

6.3 扩展功能

操作日志:记录重要操作和权限验证失败 权限审计:定期审计用户权限分配 权限回收:实现权限动态回收机制

七、总结

前端接口权限控制是提升系统安全性和用户体验的重要手段。通过合理的拦截器设计和权限管理机制,可以有效控制用户对API的访问。记住,前端权限控制只是辅助手段,真正的安全验证必须在服务端实现。

根据实际项目需求,可以选择合适的权限模型和实现方案,建议结合路由权限和按钮级权限,构建完整的前端权限体系。

相关推荐