【发布时间】:2021-10-11 15:13:21
【问题描述】:
我有一个 Web 应用程序,我已经开发了一年多一点,并进行了一些更改。前端是 react w/react-router-dom 5.2 来处理导航,一个 service worker 来处理缓存、安装和 webpush 通知,然后后端是一个 Javalin 应用程序,它存在于 Jetty 之上。
我正在使用上下文 API 来存储一些会话详细信息。当您导航到我的应用程序时,如果您尚未登录,那么您的信息还不会存储在该上下文中,因此您将被重定向到 /login 将开始该过程。 LoginLogout 组件只是重定向到处理身份验证工作流程的外部身份验证服务器,然后再重定向回另一个端点。
详情如下:
- 在服务器代码中没有重定向到 /login 并且 ProtectedRoute 代码肯定是这个问题的罪魁祸首。导航到 /login 会导致无限重定向或无限重新呈现。
- 服务器端的所有重定向都是临时使用代码 302 执行的。同样,它们都没有指向 /login
- 我认为这个问题与上下文本身有关。我已经对上下文进行了修改,现在我遇到了与以前不同的行为,当时我认为服务人员是罪魁祸首。问题仍然是无限重定向或重新呈现,很难解决。
- 我知道服务器正在做它的一部分,并且 /auth/check 端点始终提供它应该提供的内容。
这是我的 ProtectedRoute 代码
import { Redirect, Route, useLocation } from "react-router-dom";
import PropTypes from "prop-types";
import React, { useContext, useEffect, useState } from "react";
import { AuthContext } from "../Contexts/AuthProvider";
import LoadingComponent from "components/Loading/LoadingComponent";
import { server } from "variables/sitevars";
export const ProtectedRoute = ({ component: Component, ...rest }) => {
const { session, setSession } = useContext(AuthContext);
const [isLoading, setLoading] = useState(true);
const [isError, setError] = useState(false);
const cPath = useLocation().pathname;
//Create a checkAgainTime
const getCAT = (currTime, expireTime) => {
return new Date(
Date.now() + (new Date(expireTime) - new Date(currTime)) * 0.95
);
};
//See if it's time to check with the server about our session
const isCheckAgainTime = (checkTime) => {
if (checkTime === undefined) {
return true;
} else {
return Date.now() >= checkTime;
}
};
useEffect(() => {
let isMounted = true;
let changed = false;
if (isMounted) {
(async () => {
let sesh = session;
try {
//If first run, or previously not logged in, or past checkAgain
if (!sesh.isLoggedIn || isCheckAgainTime(sesh.checkAgainTime)) {
//Do fetch
const response = await fetch(`${server}/auth/check`);
if (response.ok) {
const parsed = await response.json();
//Set Login Status
if (!sesh.isLoggedIn && parsed.isLoggedIn) {
sesh.isLoggedIn = parsed.isLoggedIn;
sesh.webuser = parsed.webuser;
sesh.perms = parsed.perms;
if (sesh.checkAgainTime === undefined) {
//Set checkAgainTime if none already set
sesh.checkAgainTime = getCAT(
parsed.currTime,
parsed.expireTime
);
}
changed = true;
}
if (sesh.isLoggedIn && !parsed.isLoggedIn) {
sesh.isLoggedIn = false;
sesh.checkAgainTime = undefined;
sesh.webuser = undefined;
sesh.perms = undefined;
changed = true;
}
} else {
setError(true);
}
}
if (changed) {
setSession(sesh);
}
} catch (error) {
setError(true);
}
setLoading(false);
})();
}
return function cleanup() {
isMounted = false;
};
}, []);
if (isLoading) {
return <LoadingComponent isLoading={isLoading} />;
}
if (session.isLoggedIn && !isError) {
return (
<Route
{...rest}
render={(props) => {
return <Component {...props} />;
}}
/>
);
}
if (!session.isLoggedIn && !isError) {
return <Redirect to="/login" />;
}
if (isError) {
return <Redirect to="/offline" />;
}
return null;
};
ProtectedRoute.propTypes = {
component: PropTypes.any.isRequired,
exact: PropTypes.bool,
path: PropTypes.string.isRequired,
};
这里是 Authprovider 的使用。我还继续给登录/注销一个不同的端点:
export default function App() {
return (
<BrowserRouter>
<Switch>
<Suspense fallback={<LoadingComponent />}>
<Route path="/login" exact component={InOutRedirect} />
<Route path="/logout" exact component={InOutRedirect} />
<Route path="/auth/forbidden" component={AuthPage} />
<Route path="/auth/error" component={ServerErrorPage} />
<Route path="/offline" component={OfflinePage} />
<AuthProvider>
<ProtectedRoute path="/admin" component={AdminLayout} />
</AuthProvider>
</Suspense>
</Switch>
</BrowserRouter>
);
}
这是 AuthProvider 本身:
import React, { createContext, useState } from "react";
import PropTypes from "prop-types";
export const AuthContext = createContext(null);
import { defaultProfilePic } from "../../views/Users/UserVarsAndFuncs/UserVarsAndFuncs";
const AuthProvider = (props) => {
const [session, setSesh] = useState({
isLoggedIn: undefined,
checkAgainTime: undefined,
webuser: {
IDX: undefined,
LastName: "",
FirstName: "",
EmailAddress: "",
ProfilePic: defaultProfilePic,
},
perms: {
IDX: undefined,
Name: "",
Descr: "",
},
});
const setSession = (newSession) => {
setSesh(newSession);
};
return (
<AuthContext.Provider value={{ session, setSession }}>
{props.children}
</AuthContext.Provider>
);
};
export default AuthProvider;
AuthProvider.propTypes = {
children: PropTypes.any,
};
更新:因为它被要求,这里是我的登录/注销组件,建议的更改(与 ProtectedRoute 依赖项分开)
import React, { useEffect, useState } from "react";
import { Redirect, useLocation } from "react-router-dom";
//Components
import LoadingComponent from "components/Loading/LoadingComponent";
import { server } from "variables/sitevars";
//Component Specific Vars
export default function InOutRedirect() {
const rPath = useLocation().pathname;
const [isError, setError] = useState(false);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true;
if (isMounted) {
(async () => {
try {
//Do fetch
const response = await fetch(`${server}/auth/server/data`);
if (response.ok) {
const parsed = await response.json();
if (rPath === "/login") {
window.location.assign(`${parsed.LoginURL}`);
} else if (rPath === "/logout") {
window.location.assign(`${parsed.LogoutURL}`);
}
}
} catch (error) {
setError(true);
}
})();
setLoading(false);
}
return function cleanup() {
isMounted = false;
};
}, []);
if (isLoading) {
return <LoadingComponent />;
}
if (isError) {
return <Redirect to="/offline" />;
}
}
我怎样才能找到这个问题?
更新:我已经完成了进一步的故障排除,现在确信我使用上下文的方式有问题,并且服务工作者实际上并没有在这个问题中发挥作用。我已经更新了帖子以反映这一点。
更新 2:我做了进一步的简化。可以肯定的是,在页面呈现重定向组件并重定向回登录之前,或者完全没有通过 setSession 更新上下文。
更新 3:我相信我发现了这个问题,虽然不是正面的,但我认为它已经解决了。赏金已经提供,如果有人能解释为什么会这样,那就是你的了。
【问题讨论】:
标签: reactjs authentication react-router-dom use-state use-context