@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 */}
}}
/>
)
}
/>
);
};
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;
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