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. 实现侧边栏的角色权限控制
创建导航配置文件集中管理权限,再在侧边栏组件中过滤显示:
navConfig.js
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
相关产品推荐
相关产品推荐

