我正在我的应用程序中实现安全和用户管理,并遇到了 React router、Context、localStorage 的问题。
使用路由器,我创建了一条私有路由,并检查 Context 或 localStorage 上是否存在用户 ID,如果用户 ID 不存在,则将用户重定向到登录页面。 这很好用,但是如果我刷新页面,它总是重定向到登录页面。即使用户已登录,并且应该存在于本地存储中。
我认为问题源于 localStorage 和“useEffect()”。 我的理论是,路由器在 localStorage (和 useEffect)完成检查 userID 是否存在之前进行重定向。 不知道如何解决这个问题?
这是我的私有路由组件的代码”
import {
Route,
Redirect,
RouteProps,
} from 'react-router-dom';
import {MyContextProvider, useMyContext} from '../Context/UserContext';
interface PrivateRouteProps extends RouteProps {
// tslint:disable-next-line:no-any
component: any;
UserID: number;
}
const PrivateRoute = (props: PrivateRouteProps) => {
const { component: Component, UserID, ...rest } = props;
console.log("Inside Route: "+ UserID);
return (
<Route
{...rest}
render={(routeProps) =>
UserID > 0 ? (
<Component {...routeProps} />
) : (
<Redirect
to={{
pathname: '/',
state: { from: routeProps.location }
}}
/>
)
}
/>
);
};
export default PrivateRoute;
^ " console.log("Inside Route: "+ UserID); " 打印 0,这就是为什么它总是重定向到登录页面。这就是为什么我认为 useEffect() (如下所示的路由)可能与 userID 设置不正确有关。
路由部分代码:
function Routing() {
const classes = useStyles();
const [store, setStore] = useMyContext();
useEffect(() => { // check if user exists in localstorage, if so, set the values on react Context
const res = localStorage.getItem("UserID");
if (res) {
// since LocalStorage can return a type or null, we must check for nulls and set accordingly.
const uID = localStorage.getItem("UserID");
const UserID = uID !== null ? parseInt(uID) : 0;
const un = localStorage.getItem("Username");
const Username = un !== null ? un : '';
const iA = localStorage.getItem("IsAdmin");
const isAdmin = iA != 'true' ? true : false;
const eN = localStorage.getItem("EmailNotifications");
const emailNotifications = eN != 'true' ? true : false;
setStore({ // update the 'global' context with the logged in user data
UserID:UserID,
Username: Username,
IsAdmin: isAdmin,
EmailNotifications: emailNotifications
});
console.log(UserID);
console.log(Username);
console.log(isAdmin);
console.log("foundUser");
}
}, []);
const id = store.UserID; // get the userID from context; to pass into PrivateRoute
console.log("ID: ++"+ id);
return (
<div className="App">
<Router>
<Switch>
<Route exact path="/" component={Login}/>
<PrivateRoute path="/dashboard" UserID={id} component={Dashboard}/>
<PrivateRoute path="/add" UserID={id} component={AddTicket}/>
<PrivateRoute path="/settings" UserID={id} component={Settings}/>
<Route component={NotFound}/>
</Switch>
</Router>
</div>
);
}
export default Routing;
为了解决我想要完成的任务,我只是将 localStorage 检索直接添加到 PrivateRoute 中(而不是使用 useEffect 的 PrivateRoute 的父节点)
const PrivateRoute = (props: PrivateRouteProps) => {
const { component: Component, UserID, ...rest } = props;
console.log("Inside Route: "+ UserID);
return (
<Route
{...rest}
render={(routeProps) =>
localStorage.getItem("UserID") ? ( // if UserID == 0 when user is NOT logged in. So, if ID == 0, redirect to signinpage
<Component {...routeProps} />
) : (
<Redirect
to={{
pathname: '/',
state: { from: routeProps.location }
}}
/>
)
}
/>
);
};
将用户的 ID 存储在存储中是非常糟糕的主意。这是敏感信息。尝试实现在等待从您的服务器或您正在使用的服务器获取 id 时显示旋转器或 HTML 骨架的逻辑。 URL 可能会稍微改变,但用户只会看到微调器/HTML 骨架,这比重定向和显示用户不应该看到的页面更好的用户体验,更不用说用户的 id 保存在
LocalStorage
中。
import { useSelector } from 'react-redux'
import { Navigate, Outlet } from 'react-router'
import { selectCurrentToken } from '../features/auth/authSlice'
function PrivateRoutes() {
const token = useSelector(selectCurrentToken) || localStorage.getItem('token')
return token ? <Outlet /> : <Navigate to='/login' />
}
export default PrivateRoutes
通过将 localStorage.getItem('token') 与 useSelector(selectCurrentToken) 一起使用,我确保即使在页面刷新后令牌仍然存在,从而防止意外重定向。