【问题标题】:React Route protection with HOC PrivateRoute component with asynchronous jwt token validation使用带有异步 jwt 令牌验证的 HOC PrivateRoute 组件反应路由保护
【发布时间】:2022-01-05 14:03:59
【问题描述】:

我对 React 还不是很有经验,并且一直在尝试解决保护特定路由免受身份验证的问题。

为此,我需要实现一个 HOC 组件,特别是“PrivateRoute”组件。 一些主要的概括:

  1. 我正在使用 React 17.0.2 进行开发,然后是 react-router-dom 6.2.1

  2. 我需要验证令牌在 Redis 服务器(在云中)上是否存在并验证它。

该项目分为两个“模块”,一个在 React 中用于前端,一个在 node js/express 中,它暴露了服务器端逻辑。

在前端模块中,路由实现如下:

 return (<Router>
    <div className="App">
      <nav className="navbar navbar-expand-lg navbar-light fixed-top">
        <div className="container">
          <Link className="navbar-brand" to={"/"}>My Site</Link>
          <div className="collapse navbar-collapse" id="navbarTogglerDemo02">
            <ul className="navbar-nav ml-auto">
              <li className="nav-item">
                <Link className="nav-link" to={"/sign-in"}>Login</Link>
              </li>
              <li className="nav-item">
                <Link className="nav-link" to={"/sign-up"}>Sign up</Link>
              </li>
            </ul>
          </div>
        </div>
      </nav>
          <Routes>
            <Route exact path='/' element={<PrivateRoute><Home /></PrivateRoute>} />
            <Route path="/sign-in" element={<Login />} />
            <Route path="/sign-up" element={<SignUp />} />
            <Route path="*" Component={<Login/>} status={403}/>
          </Routes>
    </div></Router>

如您所见,例如,我希望 Home 组件受到保护,因此我实现了用于包装 Home 组件的 PrivateRoute 组件。

PrivateRoute 组件的实现必须验证 localStorage 中存在的任何 jwt 令牌,并将其传递给 node/express 模块公开的 HTTP 验证服务。令牌通过 Authorization Bearer 标头发送到 http 服务。

从 localStorage 检索并发送到验证服务的逻辑是通过 useEffect (() => {}, []) 实现的。它里面定义了异步函数,所以你可以用await进行axios调用。该函数总是从 useEffect 的主体中调用。但是,我不明白为什么更新用 useState 创建的状态变量似乎没有正确更新(同步问题?)。如果 Home 组件验证正确(在三元运算符中,home 由 props.children 表示),而不是重定向到登录页面,这似乎不会触发渲染更新。

下面我汇报一下 PrivateRoute 的实现:

export const PrivateRoute = (props) => {
    console.log('props',props);
    const [tokenValid, setTokenValid] = useState('');
    useEffect(() => {
        const checkRedisToken = async () => {
            console.log('Access localStorage');
            const ni_token = localStorage.getItem('ni_token');
            console.log('Returned localStorage');
            const config = { headers: { 'Authorization': `Bearer ${ni_token}` } };
            console.log('Call axios');
            const response = await axios.get('http://localhost:5000/identity/verifytoken', config);
            const data = response.data;
            console.log('After axios ', data);   
            setTokenValid(response.data);
    
        }
        checkRedisToken();
        console.log('tokenValid', tokenValid);
    }, []);
 
    return (
        tokenValid !== '' ?
            props.children :
            <Navigate to="/sign-in" />
    )
}

提前感谢您帮助理解我的错误。

非常感谢

【问题讨论】:

  • console.log('tokenValid', tokenValid); 不会记录新的 tokenValid 值,因为 React 状态更新不是立即的。还有checkRedisTokenasync,你不会在等待它。但状态仍应更新,组件应重新渲染。如果您发布控制台输出可能会有所帮助。
  • @tromgy 谢谢你的回复。是的你是对的。调用是异步的,所以我看不到日志的价值。但是,我会期待一个新的重新渲染。同时,阅读了一些类似的帖子,我通过引入中间状态来更改代码,这似乎有效。我如何共享代码并一起评论它?我会首先回答吗?
  • 您可以尝试在代码和框/stackblitz 等中创建一个最小的可重现示例。因为我们都同意,只要设置了tokenValid 状态,渲染仍然应该发生。
  • 嗨@tromgy,为简洁起见,我将用我目前的解决方案回复我的第一篇文章。该解决方案是网络研究和一些以前的类似帖子的结果。讨论解决方案以解释 React 的逻辑和操作对我会有所帮助,即使是那些最终发现自己在未来阅读此消息线程的人。

标签: reactjs async-await jwt authorization react-router-dom


【解决方案1】:

我尝试描述我采用的解决方案。解决方案是阅读以前发布的类似帖子和分散在网络上的阅读的结果。 正如@tromgy 所指出的,我的第一个实现的问题是由对端点/身份/验证令牌的异步调用引起的。 调用后的执行流程继续(等待结果),即使设置了 tokenValid 状态变量,React 似乎也不会立即检测到,并且不会调用新的渲染。

正确的解决方案似乎是添加一个额外的“loadingComplete”状态。

“loadingComplete”状态似乎处理了组件未安装或已安装等待已验证状态准备好和设置的阶段。

路由的声明保持不变,为简单起见,我在下面显示代码片段:

function App() {
  return (<Router>
    <div className="App">
      <nav className="navbar navbar-expand-lg navbar-light fixed-top">
        <div className="container">
          <Link className="navbar-brand" to={"/"}>My Site</Link>
          <div className="collapse navbar-collapse" id="navbarTogglerDemo02">
            <ul className="navbar-nav ml-auto">
              <li className="nav-item">
                <Link className="nav-link" to={"/sign-in"}>Login</Link>
              </li>
              <li className="nav-item">
                <Link className="nav-link" to={"/sign-up"}>Sign up</Link>
              </li>
            </ul>
          </div>
        </div>
      </nav>
          <Routes>
            <Route exact path='/' element={<PrivateRoute><Home /></PrivateRoute>} />
            <Route path="/sign-in" element={<Login />} />
            <Route path="/sign-up" element={<SignUp />} />
            <Route path="*" Component={<Login/>} status={403}/>
          </Routes>
    </div></Router>
  );
}

export default App;

如前所述,对 PrivateRoute 组件所做的更改是相当大的。在下面给出简要说明之前,我先报告一下新的实现:

    export const PrivateRoute = (props) => {
        console.log('props', props);
        const [authenticated, setAuthenticated] = useState(null);
        const [loadingComplete, setLoadingComplete] = useState(false);
        useEffect(() => {
            const ni_token = localStorage.getItem('ni_token');
            const isLogin = async (ni_token) => {
                console.log('isLogin');
                try {
                    const config = { headers: { 'Authorization': `Bearer ${ni_token}` } };
                    const result = await axios.get('http://localhost:5000/identity/verifytoken', config);
                    setAuthenticated(result.data);
                } catch (e) {
                    console.log(e);
                }
                setLoadingComplete(true);
            }
            isLogin(ni_token);
    
        }, []);
    
    
    
    
        if(loadingComplete){
            console.log('Completed', authenticated);
            return (authenticated === 'Verify Token Respone' ?
            props.children :
            <Navigate to="/sign-in" />)
        }else {
            console.log('Loading...');
            return (<div> Loading...</div>)
        }
}

如你所见,useEffect 的依赖数组为空,因此传递的函数只执行一次。

在第一次调用 web 应用程序根时,我们观察到 console.log ('props', props) 的执行。 紧接着,console.log 跟踪显示('Loading ...')。 紧接着,“isLogin”日志出现,其实useEffect调用isLogin(ni_token)。此时,对负责验证令牌的端点“/identity/verifytoken”进行异步http调用。在第一次访问前端(http://localhost:3000)时,没有令牌,并且端点返回 http 403(禁止访问)状态。认证的状态变量没有改变,它的值仍然是“null”,但是 lodingComplete 设置为“true”导致

【讨论】:

  • "react 不会重新执行 useStates"。这并不完全正确。在 first 执行时,状态设置为 initial 状态,该状态作为参数传递给 useState,但在后续渲染中 useState 实际上返回 当前状态。
  • 在你的第二个序列中发生的是组件第一次呈现loadingCompletefalse(初始),然后在useEffect 中的回调被执行,你得到“isLogin”控制台,然后调度 async axios 调用,但是渲染还在继续,仍然处于“正在加载”状态,最后 axios 调用完成,设置了两个状态,重新渲染。
  • 嗨@tromgy,感谢您的回答。加深,也感谢您的考虑,在 useEffect 回调中,我添加了一些控制台日志: c onsole.log ('Before setAuthenticated ...'); setAuthenticated (result.data);和console.log('在setLoadingComple之前......');设置加载完成(真);。从我们在第一次执行回调时观察到的日志序列中,执行堆栈在 setAuthenticated 处停止,并触发了新的渲染。此时,堆栈恢复,最新的更新加载完成。它与管理事件循环有什么关系吗?
  • axios 上的awat 完成后,setAuthenticatedsetLoadingComplete 都应该依次执行。我会在 PrivateRoute 主函数的主体中的某处添加类似 console.log(`Rendering with: authenticated ${authenticated}, loadingComplete: ${loadingComplete}`); 的内容,例如就在useEffect 之前。这应该可以让您清楚地了解渲染发生的时间和状态。
  • 是的,尽量减少重新渲染总是有意义的。在您的情况下,我认为此 PrivateRoute 组件中的 authenticated 状态是多余的,因为您具有由其父级管理的相同状态-您将 setIsAuthenticated 作为道具传递。所以,你也可以传递父状态,并在这个子组件中使用它而不是它自己的authenticated/setAuthtenticated

猜你喜欢
  • 2019-11-22
  • 2021-12-06
  • 2021-03-10
  • 1970-01-01
  • 1970-01-01
  • 2020-11-17
  • 2018-05-21
  • 2020-11-18
  • 2014-10-18
相关资源
最近更新 更多