繁体   English   中英

努力在 React 中实现 RBAC,成功登录后页面不会呈现

[英]struggling to implement RBAC in react, pages won't render after successful login

我试图在我的反应应用程序中实现 RBAC,我使用 nodejs 作为后端,它工作得很好,并且在反应中我使用 vite 和 react-router-dom v6。 为了简单起见,我决定将从我的服务器检索到的令牌和角色存储在 Session 存储中。 我的应用程序中有 2 个角色,“基本”和“管理员”,成功登录后,我根据从服务器检索到的角色进行重定向。 我有一个 HOC 作为“中间件”,也可以检查我的路由布局中的角色权限。 重定向工作正常,但是,当我测试它时,我得到了两个角色的空白页面,它们都应该呈现一个简单的 h1 标签。我错过了什么,或者在我的实现中做错了什么?

我的登录页面:

import {useState} from 'react'
import { useNavigate } from 'react-router-dom';
import axios from 'axios';

const Login = () => {
  const navigate = useNavigate()
  const [input, setInput] = useState({
    email:"",
    password:""
  })

  const handleInputChange = (e) => {
    const {name, value} = e.target;
    setInput((prev) => ({
        ...prev, [name]:value
    }))
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    console.log(input);

    const url = "http://localhost:8080/auth/login";

    try {
        const response = await axios.post(`${url}`,
        input,
        {headers:{
            'content-type':'application/json'
        }}
        );

        console.log(response);

        if(response.status === 200) {
            console.log("ok");

          const token = response.data.token;
          const role = response.data.role
          console.log({"token":token, "role":role})


          sessionStorage.setItem('token', token);
          sessionStorage.setItem('role', role);

            if(role === "admin"){
              navigate('/admin');
            } else if( role === "basic") {
              navigate('/basic');
            } else {
              navigate('/login');
            }

        }
    } catch(error) {
        console.log({"error":error.response})
    }
  }


  return (
    <div>
      <h1>Log In</h1>
        <form onSubmit={handleSubmit}>
            <p>email:</p>
            <input type="text" name="email" value={input.email} onChange={handleInputChange}/>
            <p>password:</p>
            <input type="text" name="password" value={input.password} onChange={handleInputChange}/><br/>
            <button type='submit'>Login</button>
        </form>
    </div>
  )
}

export default Login

我在 app.js 文件中的路由器布局:

import {  } from 'react'
import './App.css'
import { Route, Routes } from 'react-router'

import Access from './Auth/Access'

import Home from './pages/Home'
import Login from './pages/Login'
import Signup from './pages/Signup'
import Admin from './pages/Admin'
import Basic from './pages/Basic'
import { Error } from './pages/Error'



function App() {

  return (
    <div>
      <Routes>
        <Route path='/' element={<Home/>}/>
        <Route path='/login' element={<Login/>}/>
        <Route path='/signup' element={<Signup/>}/>
        <Route path='/basic' element={<Access role={'basic'}> <Basic/></Access>}/>
        <Route path='/admin' element={<Access role={'admin'}><Admin/></Access>}/>
        <Route path='*' element={<Error/>}/>
      </Routes>
    </div>
  )
}
export default App

我的 HOC 检查路由中的角色,名称为 Access:

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

const Access = ({ role }) => {
  const userRole = sessionStorage.getItem("role");
  const token = sessionStorage.getItem('token');
  
  if (!token || (role && role !== userRole)) {
    // Redirect to login if token is not available or user role doesn't match
    return <Navigate to="/login" replace />;
  }

  // User has the required role or no role is specified, render the nested components
  return <Outlet />;
};

export default Access

我希望这两个分页都可以根据登录的角色重定向使用简单的 h1 标签呈现。

管理页面:

/* eslint-disable no-unused-vars */
import React from "react"

const Admin = () => {
  return (
    <div>wecome to admin page</div>
  )
}

export default Admin
// eslint-disable-next-line no-unused-vars
import React from 'react'

const Basic = () => {
  return (
    <div>welcome to basic page</div>
  )
}

export default Basic

经过大量调整后,我认为我终于能够解决这个问题。我创建了一个 HOC 来接受角色,然后有条件地导航当前登录。

这是我的解决方案,但我会保持这篇文章开放,以防有人有比我更好的解决方案。

让我们从 app.js 文件中的路由器布局开始:

import {} from "react";
import "./App.css";
import { Route, Routes } from "react-router";

import Access from "./Auth/Access";

import Home from "./pages/Home";
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import Admin from "./pages/Admin";
import Basic from "./pages/Basic";
import BasicSubPage from "./pages/BasicSubPage";
import BasicSecondPage from "./pages/BasicSecondPage";
import PublicNav from "./pages/PublicNav";
import Error from "./pages/Error";

function App() {
  return (
    <div>

      <PublicNav/>
      <Routes>
        {/* public routes */}
        <Route path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />
        <Route path="/signup" element={<Signup />} />

        {/* basic protected routes */}
        <Route element={<Access role={"basic"}/>}>
          <Route path="/basic" element={<Basic/>}>
            <Route path="basicsub" element={<BasicSubPage/>}/>
            <Route path="/basic/basicsecond" element={<BasicSecondPage/>}/>
          </Route>
        </Route>

        {/* admin protected routes */}
        <Route element={<Access role={"admin"}/>}>
          <Route path="/admin" element={<Admin />} />
        </Route>

        <Route path="/error" element={<Error />} />
      </Routes>
    </div>
  );
}
export default App;

如您所见,我的 HOC 被命名为“Access”,它接受一个角色作为 prop。

这是“访问”组件:请注意:这是一个简单的验证,因为我想让事情尽可能简单。

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

const Access = ({role}) => {
  const token = sessionStorage.getItem("token");
  const userRole = sessionStorage.getItem("role");

  if(!token || token && !role.includes(userRole)) {
    return <Navigate to="/error" replace/>
  }
  
  return <div>
     <Outlet/>
  </div>;
}

export default Access

我的登录组件保持不变,所以那里没有变化,我添加到“基本”用户的子页面,所以当我可以使用“选项卡”布局在这两个页面之间导航时..

所以这是我的“基本”页面,请注意其他页面呈现了简单的 h1 标签,所以我没有在此处添加它们。

基本页面:

// eslint-disable-next-line no-unused-vars
import { useEffect, useState } from "react";
import { getInfo } from "../utils/basicUtils";
import { useNavigate , NavLink, Outlet} from "react-router-dom";

const Basic = () => {
  const navigate = useNavigate();
  const token = sessionStorage.getItem("token");

  const [personalData, setPersonalData] = useState({});

  useEffect(() => {
    if (!token) {
      sessionStorage.removeItem('token');
      sessionStorage.removeItem('role');
      navigate(0);
    }

    const getPersonalInfo = async () => {
      const url = 'http://localhost:8080/basic/info'
      const resp = await getInfo(`${url}`,token);
      
      if(resp.status === 403) {
        console.log("forbiden");
        sessionStorage.removeItem('token');
        sessionStorage.removeItem('role');
        navigate(0);
        navigate('/home');
      }
      setPersonalData(resp.data.personalInfo)
      console.log("success", resp.data.personalInfo);
    };

    getPersonalInfo()

    }, [token]);

    const logout = () => {
      sessionStorage.removeItem('token');
      sessionStorage.removeItem('role');
      navigate('/home')
    }


    return (
      // <NavLink className="Link"  to="/home">Home</NavLink>

      <div> 
      <h1>welcome {personalData.name}</h1>
      <button onClick={logout}>Logout</button>
        <nav>
          <NavLink to={"basicsub"}>basic-sub</NavLink>
          <NavLink to={"/basic/basicsecond"}>basic-sub-second</NavLink>
        </nav>
        <Outlet/>    
      </div>
    );
};
export default Basic;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM