如何在前端 React JS - MERN 应用程序上控制用户角色



MERN APP (MongoDB, Express, ReactJS, NodeJS( - 在后端,我使用令牌控制登录的用户,并根据角色授予用户访问权限。

问题是:如果用户是管理员或客户端,如何控制前端用户的角色(ReactJS(?

前任: 如果用户被记录,我会将令牌从后端发送到前端,如果用户登录,我可以控制路由如下内容:

if(token){                                //if user is logged
<Route path="/dashboard" exact>
<Products/>                          //Products Page
</Route>
}else{
<Route path="/dashboard" exact>
<Page1 />                          //AnyPage
</Route>
}

我只想为管理员用户授予对用户页面的访问权限:

if(token && role === 'admin'){                                //if user is logged and role is admin
<Route path="/dashboard" exact>
<Users />                          //Users Page
</Route>
}...

我可以在后端控制这件事,我只想在前端对这个问题提出任何其他想法。 谢谢:)

交互式示例

在这种情况下我会怎么做:

  1. 当用户登录时,API 应返回其信息,包括权限。将该信息存储在状态中。

  2. 创建一个函数,checkUserPermission(user = { roles: [] }, permission).此函数接受user对象,permission字符串(即"route.admin""component.Authenticate"(,并且应根据用户的访问级别返回 true 或 false。这将是一个决定是否允许当前用户执行此或该操作的位置。它可以包括简单的属性检查(if (user.role === 'admin')(或更复杂的东西(看例子(。

  3. 创建SecuredRoute组件,该组件是Route的包装器,但会获取userpermission参数,并在用户看不到此路由时重定向。

  4. 使用checkUserPermission函数有条件地呈现链接或组件。

  5. 正如在其他答案中已经提到的,API 必须对每个请求进行自己的权限检查,因为在 UI 端隐藏内容不会阻止恶意和知识渊博的用户


//src/utils/checkUserPermission.js
export default function checkUserPermission(user = { roles: [] }, permission) {
const allowAccessForRoles = {
"route.admin": ["admin"],
"route.authenticated": ["user", "admin"],
"route.home": ["*"], //means "Any role"
"component.Authenticate": ["*", "!user", "!admin"], //Any role except user and admin
"component.BecomeAdmin": ["user"],
"component.LogOut": ["user", "admin"]
};
//If we don't have such permission in list, access denied for everyone
if (!Array.isArray(allowAccessForRoles[permission])) {
return false;
}
//Check if any of user's roles explicitly denies access
for (const role of user.roles) {
if (allowAccessForRoles[permission].includes("!" + role)) {
return false;
}
}
//If list of allowed roles contains '*', access allowed for everyone
if (allowAccessForRoles[permission].includes("*")) {
return true;
}
//Check if any of user's roles allowes access
for (const role of user.roles) {
if (allowAccessForRoles[permission].includes(role)) {
return true;
}
}
return false;
}
//src/SecuredRoute
import React from "react";
import { Route, Redirect } from "react-router-dom";
import checkUserPermission from "./utils/checkUserPermission";
const SecuredRoute = ({
user,
permission,
redirectTo = "/",
children,
...rest
}) => {
const allowed = checkUserPermission(user, permission);
if (allowed) {
return <Route {...rest} render={() => children} />;
}
return (
<Route
{...rest}
render={({ location }) => (
<Redirect
to={{
pathname: redirectTo,
state: { from: location }
}}
/>
)}
/>
);
};
export default SecuredRoute;
//src/App.js
import React, { useState } from "react";
import "./styles.css";
import { BrowserRouter as Router, Switch, Link } from "react-router-dom";
import SecuredRoute from "./SecuredRoute";
import checkUserPermission from "./utils/checkUserPermission";
const AdminPage = () => <div>Admin page is open for admins</div>;
const AuthenticatedPage = () => (
<div>Authenticated page is open for users and admins</div>
);
const HomePage = () => <div>Home page is open for everyone</div>;
export default function App() {
const [user, setUser] = useState();
return (
<Router>
<div className="App">    
<div className="Nav">
<div>
<Link to="/">Go to Home Page</Link>
</div>
{checkUserPermission(user, "route.authenticated") ? (
<div>
<Link to="/authenticated">Go to Authenticated Page</Link>
</div>
) : null}
{checkUserPermission(user, "route.admin") ? (
<div>
<Link to="/admin">Go to Admin Page</Link>
</div>
) : null}
</div>
<div className="Controls">
{checkUserPermission(user, "component.Authenticate") ? (
<button type="button" onClick={() => setUser({ roles: ["user"] })}>
Become authenticated
</button>
) : null}
{checkUserPermission(user, "component.BecomeAdmin") ? (
<button type="button" onClick={() => setUser({ roles: ["admin"] })}>
Become admin
</button>
) : null}
{checkUserPermission(user, "component.LogOut") ? (
<button type="button" onClick={() => setUser()}>
Log out
</button>
) : null}
</div>
<div className="Main">
<Switch>
<SecuredRoute user={user} permission="route.home" exact path="/">
<HomePage />
</SecuredRoute>
<SecuredRoute
user={user}
permission="route.authenticated"
path="/authenticated"
>
<AuthenticatedPage />
</SecuredRoute>
<SecuredRoute user={user} permission="route.admin" path="/admin">
<AdminPage />
</SecuredRoute>
</Switch>
</div>
</div>
</Router>
);
}

你通常走在正确的轨道上。这种模式(检测登录和/或角色状态以及有条件地呈现组件(通常称为"受保护的路由"。

你可以做这样的事情:

const ProtectedRoute = ({showRoute, ...props}) => {
if (showRoute) {
return <Route {...props} />
} else {
// return null to simply not render the route if not logged in
return null;
// or you could return <Redirect to='/foo' /> to send them elsewhere
}
}
const App = () => {
const isLoggedIn = true;
return (
<Router>
<ProtectedRoute showRoute={isLoggedIn} path="/dashboard" component={Users} />
</Router>
);
}

或者,在某些情况下,您可能希望渲染组件的一个版本或另一个版本,具体取决于它们是否已登录。您可以使用<Switch>来确保只渲染一个组件(如果它们已登录,则不能同时渲染两个组件(

const App = () => {
const isLoggedIn = true;
return (
<Router>
<Switch>
<ProtectedRoute showRoute={isLoggedIn} path="/dashboard" component={LoggedInDashboard} />
<Route path="/dashboard" component={LoggedOutDashboard} />
</Switch>
</Router>
);
}

现在,非常重要的一点是:您仍然需要对后端的每个 API 调用或用户操作进行身份验证。基于某种状态有条件地呈现组件可以提供良好的用户体验,但它非常不安全。请记住,任何安装了 React devtools 的人都可以随意读取和写入 React 应用程序状态,因此他们可以简单地切换isLoggedIntrue或使自己成为"管理员"角色。每个 API 请求都应使用安全令牌进行身份验证。也许你已经知道所有这些,但它确实值得重复。

编辑:通过使用 React 上下文来"只知道"用户是否登录(或其他什么(并适当渲染的路由,您可以更加简化:

const userLogin = React.createContext(false);
const LoggedInRoute = (props) => {
const showRoute = useContext(userLogin);
if (showRoute) {
return <Route {...props} />
} else {
return null;
}
}
const App = () => {
const [userLoggedIn, setUserLoggedIn] = React.useState(false);
return (
<userLogin.Provider value={userLoggedIn}>
<Router>
<LoggedInRoute path="/dashboard" component={Users} />
</Router>
</userLogin.Provider>
);
}

相关内容

最新更新