使用权限创建路由

IT技术 reactjs react-router
2021-04-26 03:17:27

我正在尝试使用权限和条件重定向用户,但我不知道为什么我的重定向无法正常工作。我创建了这些类型的路线:

//not auth
<Route path="/" exact component={Login} />


//auth with admin permission
<Route path="/home" exact component={Home} isPrivate onlyAdmin />


//auth with customer permission
<Route path="/mensagens" component={Messages} isPrivate />

然后我用props来创建重定向:

({
 isPrivate = false,
 onlyAdmin = false,
})             // <-- Params

//conditions
if (signed && !isPrivate && onlyAdmin && permissionType === 'customer') {
    return <Redirect to="/relatorios" />;
}
if (signed && !isPrivate && !onlyAdmin && permissionType === 'admin') {
    return <Redirect to="/home" />;
}

我不知道我在这里做错了什么。使用这些配置,如果我更改条件添加!isPrivate,则没有任何效果,我可以使用客户权限转到“/”或管理路由。如果我删除!isPrivate,只有权限有效客户不能转到管理员(如我所愿)...但是,路由“/”仍然有效。我尝试了很多东西,但我没有成功。

1个回答

@Jonathan,我试图让你的沙箱正常工作,但大部分部件都坏了。路由的糟糕之处在于,如果没有被发现,树深处的一条路由可能会弄乱一些登录。即使是一个简单的/咬你。无论如何,我使用与您自己的用例匹配的用例重新创建了另一个沙箱。至少我是怎么做的,所以你可以得到一个想法,所以这可能非常自以为是。

链接到实时沙盒

我假设您有一个全局状态容器或上下文,可以在其中检索当前登录的用户及其权限,以及对用户进行身份验证和授权的身份验证工作流。在这种情况下,我将当前用户和权限存储在一个AuthContext包装中App Component,并最终存储到浏览器的本地存储中。(很多更好的方法来做到这一点)。

TLDR:主要授权逻辑在DashboardPage组件中

const DashboardPage = () => {
  const { currentUser } = useContext(AuthContext);
  const { permissions } = currentUser;

  {/*
     Simplify and translate the Permissions into ways that will make sense in the UI
     For example combine multiple conditions like..
     const superAdmin = signed && !isPrivate && !onlyAdmin && permissionType === 'admin'
  */}
  const canViewUsers = permissions.includes("view-users");
  const canEditUsers = permissions.includes("edit-user");
  const canManageSettings = permissions.includes("manage-settings");

  return (
    <>
      <h1>Dashboard</h1>
      <DashboardNavBar
        permissions={{
          canViewUsers,
          canEditUsers,
          canManageSettings
        }}
      />

      <Switch>
        {/* AllUsersView wont be in the tree if canViewUsers is false.
          I prefer this approach especially for SSR */}
        {canViewUsers ? (
          <Route path="/dashboard/all-users">
            <AllUsersView />
          </Route>
        ) : null}
        {/* EditUserView still in the tree even if canEditUser is false */}
        <Route path="/dashboard/edit-user">
          <EditUserView canEdit={canEditUsers} />
        </Route>
        {canManageSettings ? (
          <Route path="/dashboard/settings">
            <SettingsView hasSettings={canManageSettings} />
          </Route>
        ) : null}
        <Route path="/dashboard/">
          <DashboardSummary />
        </Route>
        {/* If false, this route will never be rendered at all e.g with SSR */}
        {superAdmin? ( {/* Avoid the {superAdmin && (<Route...>) boolean */}
          <Route path="/dashboard/heal-covid-19">
            <SuperAdminComponent superAdmin /> {/* Prop to help redirect in component */}
          </Route>
        ) : null} {/* Return Null and avoid booleans TS will be happy */}

        {/*
          Don't redirect it inline, it will force redirect all routes if false
          Conditional redirect within the component render. See > 2.
        */}
        {superAdmin? (
          <Route path="/dashboard/settings">
            <SuperAdminComponent superAdmin />
          </Route>
        ) : <Redirect to="/home"}
      </Switch>
    </>
  );
};


const SuperAdminComponent = ({ superAdmin }) => {
  {/* >2: You can conditionally check the final condition before redirecting */}
  return superAdmin ? (
    <>
      <h2>Super Admin</h2>
      <p>Healer of Covid-19, among other things.</p>
    </>
  ) : (
    <Redirect from="/dashbaord/heal-covid-19" to="/home" />
  );
};

为了打破它...

首先,我在仪表板周围使用私有路由包装器,类似于文档中Redirects(Auth)示例。


const AuthedComponents = () => {
  return (
    <Router> {/* BrowserRouter as Router */}
      <div>
        <UserProfile /> {/* Component with Session info like User Name, SignOut Btn */}
        <MainNavBar /> {/* Publicly accessible Navigation bar Component */}

        <hr />

        <Switch>
          <Route path="/login">
            <LoginPage /> {/* The Login Page that updates the Auth Context */}
          </Route>
          <PrivateRoute path="/dashboard"> {/* Wrapper to redirect private routes */}
            <DashboardPage />
          </PrivateRoute>
          {/* All other Routes outside Private are public. Add more here */}
          <Route path="/">
            <PublicPage />
          </Route>
        </Switch>
      </div>
    </Router>
  );
};

在 中PrivateRoute,您从 中检索登录状态AuthContext并检查用户是否已登录,如果已登录则呈现仪表板,否则将其重定向到登录页面。


// A wrapper for <Route> that redirects to the login
// screen if you're not yet authenticated.
const PrivateRoute = ({ children, ...rest }) => {
  const { isLoggedIn } = useContext(AuthContext);
  return (
    <Route
      {...rest}
      render={({ location }) =>
        isLoggedIn ? (
          children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location } {/* Pass this along so you bring them back */}
            }}
          />
        )
      }
    />
  );
};

AuthContext.js组件


import React, { useState } from "react";

const AuthContext = React.createContext({
  isLoggedIn: false
});

export const AuthProvider = ({ children }) => {
  // This example user context could be in your redux store.
  const users = {
    "client-1": {
      id: "client-1",
      username: "Client One",
      permissions: ["view-users"]
    },
    "admin-1": {
      id: "admin-1",
      username: "Admin One",
      permissions: ["view-users", "edit-user", "manage-settings"]
    }
  };

  const [isLoggedIn, setIsLoggedIn] = useState(
    !!localStorage.getItem("current-user")
  );
  const [currentUserId, setCurrentUserId] = useState(
    localStorage.getItem("current-user")
  );
  const fakeWait = 1000;

  const login = async ({ userId, history, from }) => {
    await setTimeout(() => {
      localStorage.setItem("current-user", userId);
      setCurrentUserId(userId);
      setIsLoggedIn(true);

      if (from.pathname === "/" || from.pathname === "/login") {
        history.push("/dashboard");
      } else {
        history.replace(from);
      }
    }, fakeWait);

    return isLoggedIn;
  };

  const logout = async () => {
    localStorage.removeItem("current-user");

    await setTimeout(() => {
      setIsLoggedIn(false);
    }, fakeWait);

    return isLoggedIn;
  };

  return (
    <AuthContext.Provider
      value={{
        isLoggedIn,
        login,
        logout,
        currentUser: users[currentUserId]
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export default AuthContext;

App.js组件

import React, { useContext } from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  NavLink,
  Redirect,
  useHistory,
  useLocation
} from "react-router-dom";
import AuthContext, { AuthProvider } from "./AuthContext";
import "./styles.scss";

const UserProfile = () => {
  const { isLoggedIn, logout, currentUser } = useContext(AuthContext);
  return isLoggedIn ? (
    <p>
      Welcome!
      <span className="user-profile">{currentUser.username}</span>
      <button className="signout" onClick={logout}>
        Sign out
      </button>
    </p>
  ) : (
    <p>You are not logged in!</p>
  );
};

const MainNavBar = () => (
  <ul className="navbar">
    <li>
      <NavLink exact to="/" activeClassName="active-link">
        Public
      </NavLink>
    </li>
    <li>
      <NavLink to="/login" activeClassName="active-link">
        Login
      </NavLink>
    </li>
    <li>
      <NavLink to="/dashboard" activeClassName="active-link">
        Dashboard
      </NavLink>
    </li>
  </ul>
);

const PublicPage = () => (
  <>
    <h1>Public</h1>
    <p>Everyone can access this...</p>
  </>
);

const DashboardNavBar = ({ permissions }) => (
  <ul className="navbar">
    <li>
      <NavLink to="/dashboard/" exact activeClassName="active-link">
        Welcome
      </NavLink>
    </li>
    <li>
      <NavLink to="/dashboard/all-users" activeClassName="active-link">
        Users
      </NavLink>
    </li>
    <li>
      <NavLink to="/dashboard/edit-user" activeClassName="active-link">
        Edit
      </NavLink>
    </li>
    {permissions.canManageSettings && (
      <li>
        <NavLink to="/dashboard/settings">Settings</NavLink>
      </li>
    )}
  </ul>
);

const DashboardSummary = () => (
  <>
    <h2>Summary</h2>
    <p>Welcome Sceren! All Authenticated Users can access this View</p>
  </>
);

const AllUsersView = () => (
  <>
    <h2>All users</h2>
    <p>User list here. View Accessble with View Permission</p>
  </>
);

const EditUserView = ({ canEdit }) =>
  canEdit ? (
    <>
      <h2>Edit User</h2>
      <p>Detais of some User to Edit</p>
      <p>View Accessble with Edit Permission</p>
    </>
  ) : (
    <Redirect to="/dashboard/" />
  );
const SettingsView = ({ hasSettings })  => {
  return hasSettings ? (
    <>
      <h2>Settings</h2>
      <p>View Accessble with Settings Permission</p>
    </>
  ) : (
    <Redirect from="/dashbaord/settings" to="/dashbaord" />
  );
};

const DashboardPage = () => {
  const { currentUser } = useContext(AuthContext);
  const { permissions } = currentUser;

  const canViewUsers = permissions.includes("view-users");
  const canEditUsers = permissions.includes("edit-user");
  const canManageSettings = permissions.includes("manage-settings");

  return (
    <>
      <h1>Dashboard</h1>
      <DashboardNavBar
        permissions={{
          canViewUsers,
          canEditUsers,
          canManageSettings
        }}
      />

      <Switch>
        {canViewUsers ? (
          <Route path="/dashboard/all-users">
            <AllUsersView />
          </Route>
        ) : null}
        <Route path="/dashboard/edit-user">
          <EditUserView canEdit={canEditUsers} />
        </Route>
        {canManageSettings ? (
          <Route path="/dashboard/settings">
            <SettingsView hasSettings={canManageSettings} />
          </Route>
        ) : null}
        <Route path="/dashboard/">
          <DashboardSummary />
        </Route>
      </Switch>
    </>
  );
};

const LoginPage = () => {
  const history = useHistory();
  let location = useLocation();
  const { isLoggedIn, login } = useContext(AuthContext);

  const { from } = location.state || { from: { pathname: "/" } };
  const { pathname } = from;

  let handleLogin = userId => {
    login({ userId, history, from });
  };

  return isLoggedIn ? (
    "you are already logged in"
  ) : (
    <div className="login-btns">
      {pathname !== "/" && (
        <p>You must log in to view the page at {pathname}</p>
      )}
      <button onClick={() => handleLogin("client-1")}>Client Logs in</button>
      <button onClick={() => handleLogin("admin-1")}>Admin Logs in</button>
    </div>
  );
};

// A wrapper for <Route> that redirects to the login
// screen if you're not yet authenticated.
const PrivateRoute = ({ children, ...rest }) => {
  const { isLoggedIn } = useContext(AuthContext);

  return (
    <Route
      {...rest}
      render={({ location }) =>
        isLoggedIn ? (
          children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )
      }
    />
  );
};

const AuthedComponents = () => {
  return (
    <Router>
      <div>
        <h1>{` 🔐 App `}</h1>
        <UserProfile />
        <MainNavBar />

        <hr />

        <Switch>
          <Route path="/login">
            <LoginPage />
          </Route>
          <PrivateRoute path="/dashboard">
            <DashboardPage />
          </PrivateRoute>
          <Route path="/">
            <PublicPage />
          </Route>
        </Switch>
      </div>
    </Router>
  );
};

const App = () => (
  <AuthProvider>
    <div className="App">
      <AuthedComponents />
    </div>
  </AuthProvider>
);

export default App;

[Styles.scss]


body {
  background: #eee;
  font-family: 'Sofia Pro Light', Lato, sans-serif;
  font-size: 16px;
  margin: 0;
  padding-top: 10vh;
  text-align: center;
}

.App {
  height: 80vh;
  padding:0;
  margin: 0 auto;
  width: 50%;
}

hr {
  border: none;
  border-bottom: solid 1px #ddd;
  margin: 1rem 0 2rem;
}

.navbar {
  display: flex;
  justify-content: center;
  list-style: none;

  li {
   a {
    padding: 1rem 1.5rem;
    text-decoration: none;

     &:hover,
     &:focus,
     &:active {
       color: #22f;
       font-weight: bold;
       background: #eef;
     }

     &.active-link{
       color: red;
       font-weight: bold;
     }
   }
  }
}

.user-profile {
  margin-left: 1rem;
  font-weight: bolder;
}

.login-btns  {
  display: flex;
  flex-direction: column;
  button {
    margin-top: 0.5rem;
  }
}

button {
  border: solid 1px #aaf;
  cursor: pointer;
  font-family: 'Sofia Pro Light', Lato, sans-serif;
  font-size: 1rem;
  padding: 1rem 2rem;
  border-radius:  0.25rem;

  &:hover,
  &:focus,
  &:active {
    color: #22f;
    font-weight: bold;
    background: #eef;
  }

  &.signout {
    padding: 0.15rem 1rem;
    margin-left: 2rem;
  }
}

再次链接到Live Sandbox