您可以使用查询字符串来实现此操作。
将query-string依赖项添加到您的react应用程序中。
每当用户尝试访问受保护的路径时,如果用户尚未登录,则必须将其重定向到登录屏幕。要做到这一点,您将执行以下操作。
history.push('/login');
但我们可以将上一个路径作为查询参数传递,方法如下。可以使用 react-router
中的 useLocation
获取之前的路径。
const prevLocation = useLocation();
history.push(`/login?redirectTo=${prevLocation}`);
现在,在成功登录后,我们可以获取之前路径传递的查询字符串。如果之前的路径为 null
,我们可以设置一个默认路径。
const handleSuccessLogin = () => {
const location = useLocation();
const { redirectTo } = queryString.parse(location.search);
history.push(redirectTo == null ? "/apps" : redirectTo);
};
@Burhan的解决方案在/login是一个简单的登录页面时效果很好,没有进一步的重定向。在我的情况下,登录页面只检查localStorage
中的凭据,如果凭据不可用或过期,则进一步重定向到IdP登录页面。
我使用react-router-dom@6
和aws-amplify
实现了联合登录/单点登录(Federated Login/SSO),以下代码片段演示了它的工作方式。注意,它们不完整或可运行,只是解释了它的工作方式。
PrivateRoute
(有关详细信息,请参见react-router-dom@6文档)检查是否存在登录凭据(auth
是通过单独的AuthContext提供的,这超出了本主题的范围):const PrivateRoute: React.FC<{user: AuthenticatedUser}> = ({ user }) => {
const location = useLocation();
return user ? <Outlet /> : <Navigate to="/login" replace state={{ from: location }} />;
};
PrivateRoute
到 /
:return (
<Routes>
<Route path="/" element={<PrivateRoute user={auth.user} />}>
... // Other routes
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<NotFound />} />
</Routes>
);
aws-amplify
开始联合登录流程之前,将原始URL存储到sessionStorage
中: const login = async () => {
sessionStorage.setItem('beforeLogin', location.state?.from?.pathname);
await Auth.federatedSignIn();
};
sessionStorage
获取位置状态和 URL,并在调用身份验证上下文后使用其中任何一个来设置已认证的用户: Auth.currentAuthenticatedUser()
.then((currentUser) => {
if (currentUser) {
auth.setUser(currentUser);
const beforeLoginUrl = sessionStorage.getItem('beforeLogin');
sessionStorage.removeItem('beforeLogin');
navigate(beforeLoginUrl ?? location.state?.from ?? '/default');
}
})
.catch(console.error);
我需要检查两个方面,因为在我的情况下,如果登录组件找到有效的凭据,它根本不会重定向到IdP,因此sessionStorage
中没有URL,只有location.state?.from
。
我修改了 @Bonttimo 的第一个解决方案,将整个 location
对象传递给状态,以便您可以保留原始 URL 中的搜索查询。
登录按钮:
import { Link, useLocation } from "react-router-dom";
export const Navigation: FC = () => {
const location = useLocation();
return (
<Link to="/accounts/login" state={{ redirectTo: location }}>
<Button text="Login" />
</Link>
)
}
import React, { FC, useContext } from "react";
import { Navigate, Outlet, useLocation } from "react-router-dom";
import { UserContext } from "../contexts/user.context";
type Props = {
redirectPath?: string;
};
export const ProtectedRoute: FC<Props> = ({ redirectPath = "/accounts/login" }) => {
const location = useLocation();
const { currentUser } = useContext(UserContext);
if (!currentUser) {
return <Navigate to={redirectPath} replace state={{ redirectTo: location }} />;
}
return <Outlet />;
};
type RedirectLocationState = {
redirectTo: Location;
};
export const Login = () => {
const navigate = useNavigate();
const { state: locationState } = useLocation();
const onLogin = () => {
if (locationState) {
// state is any by default
const { redirectTo } = locationState as RedirectLocationState;
navigate(`${redirectTo.pathname}${redirectTo.search}`);
}
}
return (...)
}
这使您可以保留原始URL不变。
例如,有人向您发送了一个链接/users/list?country=13
,但只有经过身份验证的用户才能查看用户列表页面。此解决方案将重定向您到登录页面,并在登录后将您带回/users/list?country=13
。
在React路由器v6中,有两种简单的方法可以实现这一点,一种是在链接状态中传递先前的位置,另一种是使用useNavigate钩子。
第一种解决方案(在链接状态中传递先前的位置)
import { NavLink, useLocation } from "react-router-dom";
export default function App() {
const location = useLocation();
return (
<NavLink to="/login" state={{ prev: location.pathname }}>
Login
</NavLink>
);
}
然后在登录组件中提取状态
import { useNavigate, useLocation } from "react-router-dom";
export default function Login() {
const navigate = useNavigate();
const { state } = useLocation();
// Then when the user logs in you redirect them back with
const onLogin = () => navigate(state);
return (...)
};
第二种解决方案(仅使用 useNavigate 钩子)
import { useNavigate } from "react-router-dom";
export default function Login() {
const navigate = useNavigate();
// Then when the user logs in you redirect them back with the -1
// means go one page back in the history. you can also pass -2...
const onLogin = () => navigate(-1);
return (...)
};
import { Navigate, Outlet,} from "react-router-dom";
const PrivateRouters = () => {
let userAuth = true ;
return (
userAuth ? <Outlet/> : <Navigate to="/login" />
)
}
export default PrivateRouters;
第一步导入:
import { useNavigate } from "react-router-dom";
第二步变量声明:
const navigate = useNavigate();
第三步用户提交函数:
navigate("/pathname");
useLocation()
的返回值是一个 Location 对象,而不是字符串。 当你执行history.push(
/login?redirectTo=${prevLocation});
时,你将一个 Location 对象编码为字符串。你正在推送/login?redirectTo=[object Object]
。 - Marco Castanho/login
进一步重定向到另一个URL(例如,在企业/SaaS场景中非常常见的IDP登录页面),则此方法无效。 - Eric Xin Zhang