You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ReactJS基于角色的侧边栏导航:改造PrivateRoute实现权限控制

基于角色的权限控制改造方案

1. 完善AuthContext中的角色信息获取

首先确保从JWT中正确提取用户角色,假设你的JWT payload里包含role字段(若角色存储在groups数组,可调整为取对应值)。修改AuthContext.js中的角色初始化与更新逻辑:

// AuthContext.js
import { createContext, useState, useEffect } from 'react'
import jwt_decode from "jwt-decode";
import { useHistory } from 'react-router-dom'

const AuthContext = createContext()
export default AuthContext;

export const AuthProvider = ({children}) => {
    let [authTokens, setAuthTokens] = useState(()=> localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null)
    let [user, setUser] = useState(()=> localStorage.getItem('authTokens') ? jwt_decode(localStorage.getItem('authTokens')) : null)
    let [userRole, setUserRole] = useState(()=> {
        const tokens = localStorage.getItem('authTokens');
        if(tokens){
            const decoded = jwt_decode(tokens);
            // 从JWT中提取角色,根据实际字段调整
            return decoded.role || null;
        }
        return null;
    })
    let [loading, setLoading] = useState(true)

    const history = useHistory()

    let loginUser = async (e )=> {
        e.preventDefault()
        let response = await fetch('authentication/api/token/', {
            method:'POST',
            headers:{
                'Content-Type':'application/json'
            },
            body:JSON.stringify({'username':e.target.username.value, 'password':e.target.password.value})
        })
        let data = await response.json()

        if(response.status === 200){
            setAuthTokens(data)
            const decodedUser = jwt_decode(data.access)
            setUser(decodedUser)
            setUserRole(decodedUser.role) // 登录时同步角色
            localStorage.setItem('authTokens', JSON.stringify(data))
            history.push('/dashboards/index')
        }else{
            alert('Invalid username or password!')
        }
    }

    let logoutUser = () => {
        setAuthTokens(null)
        setUser(null)
        setUserRole(null)
        localStorage.removeItem('authTokens')
        history.push('/login/login-page')
    }

    let updateToken = async ()=> {
        let response = await fetch('api/token/refresh/', {
            method:'POST',
            headers:{
                'Content-Type':'application/json'
            },
            body:JSON.stringify({'refresh':authTokens?.refresh})
        })

        let data = await response.json()
        
        if (response.status === 200){
            setAuthTokens(data)
            const decodedUser = jwt_decode(data.access)
            setUser(decodedUser)
            setUserRole(decodedUser.role) // 更新token时同步角色
            localStorage.setItem('authTokens', JSON.stringify(data))
        }else{
            logoutUser()
        }

        if(loading){
            setLoading(false)
        }
    }

    let contextData = {
        user:user,
        userRole:userRole,
        authTokens:authTokens,
        loginUser:loginUser,
        logoutUser:logoutUser,
    }

    useEffect(()=> {
        if(loading){
            updateToken()
        }

        let fourMinutes = 1000 * 60 * 4
        let interval =  setInterval(()=> {
            if(authTokens){
                updateToken()
            }
        }, fourMinutes)
        return ()=> clearInterval(interval)
    }, [authTokens, loading])

    return(
        <AuthContext.Provider value={contextData} >
            {children}
        </AuthContext.Provider>
    )
}

2. 改造PrivateRoute为角色控制路由

修改PrivateRoute.js,新增requiredRoles属性,验证用户角色是否在允许范围内:

// PrivateRoute.js
import { Route, Redirect } from 'react-router-dom'
import { useContext } from 'react'
import AuthContext from '../Context/AuthContext'

const PrivateRoute = ({ children, requiredRoles, ...rest }) => {
    const { user, userRole } = useContext(AuthContext)

    // 未登录跳转登录页
    if (!user) {
        return <Redirect to="/login/login-page" />
    }

    // Admin角色默认拥有所有权限
    if (userRole === 'Admin') {
        return <Route {...rest}>{children}</Route>
    }

    // 验证当前角色是否在允许列表中
    const hasPermission = requiredRoles.includes(userRole)
    if (!hasPermission) {
        // 无权限跳转至Dashboard,可替换为403页面
        return <Redirect to="/dashboards/index" />
    }

    return <Route {...rest}>{children}</Route>
}

export default PrivateRoute;

3. 更新AppMain中的路由配置

给每个PrivateRoute添加对应requiredRoles,匹配权限规则:

// AppMain/index.js
import { Route, Redirect } from "react-router-dom";
import React, { Suspense, lazy, Fragment } from "react";
import { ToastContainer } from "react-toastify";
import PrivateRoute from '../../Login/Utils/PrivateRoute'
import { AuthProvider } from '../../Login/Context/AuthContext'

const Dashboards = lazy(() => import("../../Dashboard/index"));
const AccountList = lazy(() => import("../../Account/Pages/AccountList"));
const AssetList = lazy(() => import("../../Asset/Pages/AssetList"))
const AccessScanners = lazy(() => import("../../Scanner/Pages/AccessScanner"));
const ScanScheduler = lazy(() => import("../../Scanner/Pages/ScanScheduler"));
const ScanLogsNav = lazy(() => import("../../Scanner/Pages/ScanLogs"));
const AccessList = lazy(() => import ("../../Access/Pages/AccessList"))
const LoginPage = lazy(() => import ("../../Login/Pages/LoginPage"))

const AppMain = () => {
  return (
    <Fragment>
      <AuthProvider>
        <Suspense
          fallback={
            <div className="loader-container">
              <div className="loader-container-inner">
                <h6 className="mt-5">
                  Please wait while we load all the Components
                  <small>
                    Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
                  </small>
                </h6>
              </div>
            </div>
          }
        >
          <Route path="/login/login-page" component={LoginPage} />
        </Suspense>

        {/* Access - 允许Admin、Report_User */}
        <Suspense fallback={/* ... */}>
          <PrivateRoute 
            path="/access/access-list" 
            component={AccessList} 
            requiredRoles={['Admin', 'Report_User']} 
          />
        </Suspense>

        {/* Account - 允许Admin、Report_User */}
        <Suspense fallback={/* ... */}>
          <PrivateRoute 
            path="/account/account-list" 
            component={AccountList} 
            requiredRoles={['Admin', 'Report_User']} 
          />
        </Suspense>

        {/* Asset - 允许Admin、Report_User */}
        <Suspense fallback={/* ... */}>
          <PrivateRoute 
            path="/asset/asset-list" 
            component={AssetList} 
            requiredRoles={['Admin', 'Report_User']} 
          />
        </Suspense>

        {/* Scanner相关 - 仅允许Admin */}
        <Suspense fallback={/* ... */}>
          <PrivateRoute 
            path="/scanner/access-scanners" 
            component={AccessScanners} 
            requiredRoles={['Admin']} 
          />
        </Suspense>

        <Suspense fallback={/* ... */}>
          <PrivateRoute 
            path="/scanner/scan-scheduler" 
            component={ScanScheduler} 
            requiredRoles={['Admin']} 
          />
        </Suspense>

        <Suspense fallback={/* ... */}>
          <PrivateRoute 
            path="/scanner/scan-logs" 
            component={ScanLogsNav} 
            requiredRoles={['Admin']} 
          />
        </Suspense>

        {/* Dashboard - 允许Admin、Dashboard_User */}
        <Suspense fallback={/* ... */}>
          <PrivateRoute 
            path="/dashboards" 
            component={Dashboards} 
            requiredRoles={['Admin', 'Dashboard_User']} 
          />
        </Suspense>

        <Route exact path="/" render={() => <Redirect to="/login/login-page" />} />
      </AuthProvider>
      <ToastContainer />
    </Fragment>
  );
};

export default AppMain;

4. 实现侧边栏的角色权限控制

创建导航配置文件集中管理权限,再在侧边栏组件中过滤显示:

export const navItems = [
  {
    name: 'Dashboard',
    path: '/dashboards/index',
    allowedRoles: ['Admin', 'Dashboard_User']
  },
  {
    name: 'Accounts',
    path: '/account/account-list',
    allowedRoles: ['Admin', 'Report_User']
  },
  {
    name: 'Assets',
    path: '/asset/asset-list',
    allowedRoles: ['Admin', 'Report_User']
  },
  {
    name: 'Accesses',
    path: '/access/access-list',
    allowedRoles: ['Admin', 'Report_User']
  },
  {
    name: 'Access Scanner',
    path: '/scanner/access-scanners',
    allowedRoles: ['Admin']
  },
  {
    name: 'Scan Scheduler',
    path: '/scanner/scan-scheduler',
    allowedRoles: ['Admin']
  },
  {
    name: 'Scan Logs',
    path: '/scanner/scan-logs',
    allowedRoles: ['Admin']
  }
];

侧边栏组件(示例:Sidebar.js)

import { useContext } from 'react';
import { navItems } from './navConfig';
import AuthContext from '../Context/AuthContext';
import { Link } from 'react-router-dom';

const Sidebar = () => {
  const { userRole } = useContext(AuthContext);

  // 过滤当前角色可访问的导航项
  const visibleNavItems = navItems.filter(item => {
    if (userRole === 'Admin') return true;
    return item.allowedRoles.includes(userRole);
  });

  return (
    <div className="sidebar">
      <ul>
        {visibleNavItems.map((item, index) => (
          <li key={index}>
            <Link to={item.path}>{item.name}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Sidebar;

关键注意事项

  • 若JWT中角色存储在groups数组(如Django JWT默认格式),需调整AuthContext中的角色提取逻辑,例如decoded.groups.find(group => ['Admin', 'Dashboard_User'].includes(group))。
  • 无权限跳转可根据需求替换为自定义403页面,只需创建对应组件并修改PrivateRoute中的重定向路径。
  • 导航配置集中管理,后续修改权限规则只需调整navConfig.js和路由的requiredRoles即可。

内容的提问来源于stack exchange,提问作者Ricky Aguilar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 13:24:51