如何用Context API和React Query在React中实现角色授权
React 基于角色的授权实现方案
关于JWT客户端解码的安全性
首先明确:客户端用JWT-decoder解码token是安全的。因为JWT的payload部分是base64编码(不是加密),本身就是公开可读的,解码只是把编码转成明文,不存在泄露敏感信息的问题。但必须注意:客户端的角色检查仅用于前端UI控制和路由跳转,真正的权限校验必须在后端接口层完成——不能依赖前端检查保护数据,恶意用户可轻易绕过前端逻辑。
现有方案优化与落地步骤
你已经能从API获取角色并存入Context,接下来可按以下步骤完成权限控制:
1. 完善AuthContext设计
确保Context包含支撑授权逻辑的核心状态与方法:
import { createContext, useContext, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [token, setToken] = useState(localStorage.getItem('authToken')); const [userRoles, setUserRoles] = useState([]); // 依赖token拉取用户角色 const { data: fetchedRoles } = useQuery({ queryKey: ['userRoles'], queryFn: () => fetch('/api/user/roles', { headers: { Authorization: `Bearer ${token}` } }).then(res => res.json()), enabled: !!token, onSuccess: (roles) => setUserRoles(roles) }); // 登录逻辑:保存token与初始角色 const login = (newToken, initialRoles) => { localStorage.setItem('authToken', newToken); setToken(newToken); setUserRoles(initialRoles); }; // 登出逻辑:清空状态 const logout = () => { localStorage.removeItem('authToken'); setToken(null); setUserRoles([]); }; return ( <AuthContext.Provider value={{ token, userRoles, login, logout }}> {children} </AuthContext.Provider> ); }; export const useAuth = () => useContext(AuthContext);
登录时可选择让后端直接返回token和角色(减少一次请求),或登录后用React Query单独拉取角色,两种方式均可行,依后端接口设计而定。
2. 实现角色校验的受保护路由
基于React Router封装权限检查组件,比如RoleProtectedRoute:
import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from './AuthContext'; export const RoleProtectedRoute = ({ requiredRoles }) => { const { userRoles, token } = useAuth(); // 未登录跳转登录页 if (!token) { return <Navigate to="/login" replace />; } // 检查是否拥有所需角色 const hasPermission = requiredRoles.some(role => userRoles.includes(role)); if (!hasPermission) { return <Navigate to="/unauthorized" replace />; } return <Outlet />; };
路由配置中使用示例:
import { Routes, Route } from 'react-router-dom'; import { RoleProtectedRoute } from './RoleProtectedRoute'; import Dashboard from './Dashboard'; import Unauthorized from './Unauthorized'; import Login from './Login'; function App() { return ( <Routes> <Route path="/login" element={<Login />} /> <Route path="/unauthorized" element={<Unauthorized />} /> // 需要especialidades角色的路由组 <Route element={<RoleProtectedRoute requiredRoles={['especialidades']} />}> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/special-page" element={<SpecialPage />} /> </Route> </Routes> ); }
3. 组件内的权限控制
在组件中通过useAuth钩子获取角色,控制UI显示:
import { useAuth } from './AuthContext'; function Dashboard() { const { userRoles } = useAuth(); return ( <div> <h1>Dashboard</h1> {userRoles.includes('especialidades') && ( <button>仅专科用户可见的操作按钮</button> )} </div> ); }
4. 后端兜底校验(核心!)
前端控制仅提升用户体验,所有敏感接口必须在后端做权限校验:
- 先验证JWT的签名与有效期
- 从token payload或数据库中获取用户角色
- 检查用户是否拥有接口访问权限,无权限则返回403状态码
Node.js/Express中间件示例:
const verifyRole = (requiredRole) => { return (req, res, next) => { // 假设已通过JWT中间件解析出用户信息到req.user if (!req.user.roles.includes(requiredRole)) { return res.status(403).json({ message: '无权限访问' }); } next(); }; }; // 接口使用权限校验中间件 app.get('/api/special-data', verifyToken, verifyRole('especialidades'), (req, res) => { // 返回敏感数据 });
你的疑问解答
- 是否需要修改Context?如果当前Context仅存储角色,建议补充token状态、登录/登出方法,方便全局状态管理。
- 登录时同时返回token和角色可行吗?完全可以,能减少一次API请求,提升用户体验。
- 存入Context后下一步?按上述方案实现路由保护与组件内权限控制,同时确保后端完成兜底校验。
- 这是不是最佳方案?对于中小项目,Context + React Query的组合简洁高效;大型项目可考虑Redux Toolkit等状态管理库,但核心逻辑一致。
内容的提问来源于stack exchange,提问作者yzkael
相关产品推荐
相关产品推荐

