如何使用 react-router-dom 创建受保护的路由?

How to create a protected route with react-router-dom?

提问人:learner62 提问时间:2/20/2021 最后编辑:Drew Reeselearner62 更新时间:9/19/2023 访问量:30874

问:

如何使用 localStorage 创建受保护的路由并将其存储在 localStorage 中,以便当用户下次尝试打开时,他们可以再次查看其详细信息。登录后,他们应重定向到仪表板页面。react-router-dom

所有功能都添加到 ContextApi 中。 Codesandbox link : 代码

我尝试过但未能实现

“路由”页

import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";

function Routes() {
  const { authLogin } = useContext(globalC);
  console.log("authLogin", authLogin);

  return (
    <BrowserRouter>
      <Switch>
        {authLogin ? (
          <>
            <Route path="/dashboard" component={Dashboard} exact />
            <Route exact path="/About" component={About} />
          </>
        ) : (
          <Route path="/" component={Login} exact />
        )}

        <Route component={PageNotFound} />
      </Switch>
    </BrowserRouter>
  );
}
export default Routes;

上下文页面

import React, { Component, createContext } from "react";
import axios from "axios";

export const globalC = createContext();

export class Gprov extends Component {
  state = {
    authLogin: null,
    authLoginerror: null
  };
  componentDidMount() {
    var localData = JSON.parse(localStorage.getItem("loginDetail"));
    if (localData) {
      this.setState({
        authLogin: localData
      });
    }
  }

  loginData = async () => {
    let payload = {
      token: "ctz43XoULrgv_0p1pvq7tA",
      data: {
        name: "nameFirst",
        email: "internetEmail",
        phone: "phoneHome",
        _repeat: 300
      }
    };
    await axios
      .post(`https://app.fakejson.com/q`, payload)
      .then((res) => {
        if (res.status === 200) {
          this.setState({
            authLogin: res.data
          });
          localStorage.setItem("loginDetail", JSON.stringify(res.data));
        }
      })
      .catch((err) =>
        this.setState({
          authLoginerror: err
        })
      );
  };
  render() {
    // console.log(localStorage.getItem("loginDetail"));
    return (
      <globalC.Provider
        value={{
          ...this.state,
          loginData: this.loginData
        }}
      >
        {this.props.children}
      </globalC.Provider>
    );
  }
}
javascript reactjs 反应路由器 react-router-dom

评论


答:

45赞 Drew Reese 2/20/2021 #1

问题

<BrowserRouter>
  <Switch>
    {authLogin ? (
      <>
        <Route path="/dashboard" component={Dashboard} exact />
        <Route exact path="/About" component={About} />
      </>
    ) : (
      <Route path="/" component={Login} exact />
    )}

    <Route component={PageNotFound} />
  </Switch>
</BrowserRouter>

不处理除 和 组件之外的任何内容。如果你想像这样“嵌套”,那么你需要将每个路由包装在通用路由中,但这完全没有必要。SwitchRouteRedirect

您的登录组件也不会处理重定向回最初访问的任何“主页”页面或专用路由。

溶液

react-router-dom6 版

在版本 6 中,自定义路由组件已失宠,首选方法是使用身份验证布局组件。

import { Navigate, Outlet } from 'react-router-dom';

const PrivateRoutes = () => {
  const location = useLocation();
  const { authLogin } = useContext(globalC);

  if (authLogin === undefined) {
    return null; // or loading indicator/spinner/etc
  }

  return authLogin 
    ? <Outlet />
    : <Navigate to="/login" replace state={{ from: location }} />;
}

...

<BrowserRouter>
  <Routes>
    <Route path="/" element={<PrivateRoutes />} >
      <Route path="dashboard" element={<Dashboard />} />
      <Route path="about" element={<About />} />
    </Route>
    <Route path="/login" element={<Login />} />
    <Route path="*" element={<PageNotFound />} />
  </Routes>
</BrowserRouter>

const routes = [
  {
    path: "/",
    element: <PrivateRoutes />,
    children: [
      {
        path: "dashboard",
        element: <Dashboard />,
      },
      {
        path: "about",
        element: <About />
      },
    ],
  },
  {
    path: "/login",
    element: <Login />,
  },
  {
    path: "*",
    element: <PageNotFound />
  },
];

...

export default function Login() {
  const location = useLocation();
  const navigate = useNavigate();
  const { authLogin, loginData } = useContext(globalC);

  useEffect(() => {
    if (authLogin) {
      const { from } = location.state || { from: { pathname: "/" } };
      navigate(from, { replace: true });
    }
  }, [authLogin, location, navigate]);

  return (
    <div
      style={{ height: "100vh" }}
      className="d-flex justify-content-center align-items-center"
    >
      <button type="button" onClick={loginData} className="btn btn-primary">
        Login
      </button>
    </div>
  );
}

react-router-dom5 版

创建一个使用身份验证上下文的组件。PrivateRoute

const PrivateRoute = (props) => {
  const location = useLocation();
  const { authLogin } = useContext(globalC);

  if (authLogin === undefined) {
    return null; // or loading indicator/spinner/etc
  }

  return authLogin ? (
    <Route {...props} />
  ) : (
    <Redirect
      to={{
        pathname: "/login",
        state: { from: location }
      }}
    />
  );
};

更新组件以处理重定向回正在访问的原始路由。Login

export default function Login() {
  const location = useLocation();
  const history = useHistory();
  const { authLogin, loginData } = useContext(globalC);

  useEffect(() => {
    if (authLogin) {
      const { from } = location.state || { from: { pathname: "/" } };
      history.replace(from);
    }
  }, [authLogin, history, location]);

  return (
    <div
      style={{ height: "100vh" }}
      className="d-flex justify-content-center align-items-center"
    >
      <button type="button" onClick={loginData} className="btn btn-primary">
        Login
      </button>
    </div>
  );
}

在“平面列表”中呈现所有路线

function Routes() {
  return (
    <BrowserRouter>
      <Switch>
        <PrivateRoute path="/dashboard" component={Dashboard} />
        <PrivateRoute path="/About" component={About} />
        <Route path="/login" component={Login} />
        <Route component={PageNotFound} />
      </Switch>
    </BrowserRouter>
  );
}

编辑 how-to-create-a-protected-route-in-react-using-react-router-dom

评论

0赞 learner62 2/22/2021
嗨,先生。我已经尝试了您的代码,它工作正常。但我面临一些问题。对我来说,没有主页,登录页面是默认页面,一旦用户尝试根据响应登录,它将导航到仪表板,并且侧边栏仅在受保护的路由中可用。Codesandbox 链接:codesandbox.io/s/protuctedroute-d26tz @Drew
0赞 Drew Reese 2/23/2021
@learner62 我的沙盒中的“home”路由只是为了提供一个起点,而不是尝试启动身份验证流。不过,我要告诫不要将“/”路径作为身份验证路径。话虽如此,您的沙盒似乎正在工作,您只需要确保不会在登录路径“/”和重定向回“/”之间创建渲染循环。PrivateRoute
0赞 Ufenei augustine 7/30/2021
如果您已登录并在受保护的路由上刷新页面,它将使用上述代码将您从页面中带走。这是因为在刷新期间,上下文最初是 null,然后才从数据库中冻结。对如何纠正这个问题有什么建议吗?
1赞 Drew Reese 7/31/2021
@Ufeneiaugustine 添加第三个加载或挂起的“状态”,该状态既不是“经过身份验证”也不是“未经身份验证”,因此您可以呈现 null、加载微调器或任何其他“处理”UI,直到确定是否应允许用户进入页面或是否应将其退回。
1赞 Drew Reese 9/24/2023
@BeniKeyserman 如果我理解正确,您可以使用路由重定向到已知/已处理的路由,例如设置默认路由。布局路由不需要在路径上,实际上,如果不需要布局路由参与路由匹配,布局路由可以完全无路径"/"PrivateRoutes"/"
8赞 Yauhen 2/2/2022 #2

对于 v6:

import { Routes, Route, Navigate } from "react-router-dom";

function App() {
  return (
    <Routes>
      <Route path="/public" element={<PublicPage />} />
      <Route
        path="/protected"
        element={
          <RequireAuth redirectTo="/login">
            <ProtectedPage />
          </RequireAuth>
        }
      />
    </Routes>
  );
}

function RequireAuth({ children, redirectTo }) {
  let isAuthenticated = getAuth();
  return isAuthenticated ? children : <Navigate to={redirectTo} />;
}

文档链接:https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f

评论

0赞 Prakash Raj 3/5/2023
我不知道为什么只有这段代码有效!!上述所有 V6 解决方案都不起作用。无论如何,谢谢@Yauhen
0赞 user2623825 3/20/2023
这在从 auth0 返回时给了我一个无限循环,它似乎继续尝试进行身份验证。
0赞 Nikita Kumari 5/21/2022 #3

import { v4 as uuidv4 } from "uuid";

const routes = [
    {
        id: uuidv4(),
        isProtected: false,
        exact: true,
        path: "/home",
        component: param => <Overview {...param} />,
    },
    {
        id: uuidv4(),
        isProtected: true,
        exact: true,
        path: "/protected",
        component: param => <Overview {...param} />,
        allowed: [...advanceProducts], // subscription
    },
    {
        // if you conditional based rendering for same path
        id: uuidv4(),
        isProtected: true,
        exact: true,
        path: "/",
        component: null,
        conditionalComponent: true,
        allowed: {
            [subscription1]: param => <Overview {...param} />,
            [subscription2]: param => <Customers {...param} />,
        },
    },
]

// Navigation Component
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";

// ...component logic
<Switch>
    {routes.map(params => {
        return (
            <ProtectedRoutes
                exact
                routeParams={params}
                key={params.path}
                path={params.path}
            />
        );
    })}
    <Route
        render={() => {
            props.setHideNav(true);
            setHideHeader(true);
            return <ErrorPage type={404} />;
        }}
    />
</Switch>

// ProtectedRoute component
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";

const ProtectedRoutes = props => {
    const { routeParams } = props;
    const currentSubscription = 'xyz'; // your current subscription;
    if (routeParams.conditionalComponent) {
        return (
            <Route
                key={routeParams.path}
                path={routeParams.path}
                render={routeParams.allowed[currentSubscription]}
            />
        );
    }
    if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
        return (
            <Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
        );
    }
    if (!routeParams.isProtected) {
        return (
            <Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
        );
    }
    return null;
};

export default ProtectedRoutes;

想要添加突出显示,永远不要忘记将路径作为 ProtectedRoute 的道具,否则它将无法正常工作。

0赞 cmoshe 8/1/2022 #4

这是一个简单的 react-router v6 保护路由。我已经将所有要保护的路由放在路由.js中:-

const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]

要渲染路由,只需按如下方式映射它们: -

<Routes>
   {routes.map((routes, id) => {
     return(
     <Route
         key={id}
         path={route.path}
         exact={route.exact}
         name={route.name}
         element={
            localStorage.getItem("token") ? (
                route.element
             ) : (
                <Navigate to="/login" />
             )
         }
     )
   })
  }
</Routes>
1赞 Harsh Tripathi 12/3/2022 #5

如果您想要一种简单的实现方法,请使用 App.js 中的登录,如果用户已登录,则设置用户变量。如果设置了用户变量,则启动这些路由,否则它将卡在登录页面。我在我的项目中实现了这一点。

  return (
    <div>
      <Notification notification={notification} type={notificationType} />

      {
        user === null &&
        <LoginForm startLogin={handleLogin} />
      }

      {
        user !== null &&
        <NavBar user={user} setUser={setUser} />
      }

      {
        user !== null &&
        <Router>
          <Routes>
            <Route exact path="/" element={<Home />} />
            <Route exact path="/adduser" element={<AddUser />} /> />
            <Route exact path="/viewuser/:id" element={<ViewUser />} />
          </Routes>
        </Router>
      }

    </div>
  )
0赞 ritik Kumar Sharma 6/8/2023 #6
import { BrowserRouter as Router } from "react-router-dom";        
import { Routes, Route } from "react-router-dom";      
import Home from "./component/home/Home.js";      
import Products from "./component/Products/Products.js";      
import Profile from "./component/user/Profile.js";      
      
import ProtectedRoute from "./component/Route/ProtectedRoute";      
function App() {      
 return (      
    <Router>      
    <Routes>      
        <Route path="/" element={<Home />} />      
        <Route path="/products" element={<Products />} />      
        <Route      
          path="/account"      
          element={      
            <ProtectedRoute  >      
              <Profile />      
            </ProtectedRoute>      
          }      
        />      
      </Routes>      
       </Router>      
  );      
}      
      
//ProtectedRoute       

export default App;      
import React from "react";      
import { useSelector } from "react-redux";      
import { Navigate } from "react-router-dom";      

function ProtectedRoute({ children }) {      
  const { isAuthecated, loading } = useSelector((state) => state.user);      
      
  if (!isAuthecated) {      
    return <Navigate to="/login" replace />;      
  }      
  return children;      
}      
export default ProtectedRoute;      
      

评论

1赞 Community 6/8/2023
正如目前所写的那样,你的答案尚不清楚。请编辑以添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。您可以在帮助中心找到有关如何写出好答案的更多信息。
1赞 Super Kai - Kazuya Ito 6/9/2023
您应该添加解释。
1赞 GMKHussain 6/29/2023 #7

这个答案是针对 reactjs 的新版本

React 路由器 Dom: ^ v6

//App.js
<Route path="/"
       element={<HomePage /> } />

<Route path="/dashboard"
       element={<AuthMiddleware> <DashboardPage /> </AuthMiddleware>}
/>
// AuthMiddelware.js
import { Navigate } from "react-router-dom"

export const AuthMiddleware = (props) => { 
    
    const token = true; // get login status here
    
    const { auth=token, children, redirect = '/login' } = props; 

    if (!auth) {
       return <Navigate to={redirect} replace />;
    }
    return children;
 };

评论

0赞 David Dehghan 8/23/2023
这对我有用。谢谢。