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

gdx19jrr  于 2023-06-05  发布在  Node.js
关注(0)|答案(1)|浏览(468)

我试图在我的react应用程序中实现RBAC,我使用nodejs作为后端,它工作得很好,在react中我使用vite和react-router-dom v6。为了简单起见,我决定将从服务器检索到的令牌和角色存储在会话存储中。我在我的应用程序中有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标记呈现,基于登录的角色重定向。
Admin page:

/* 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
3zwjbxry

3zwjbxry1#

经过大量的调整,我想我终于能够解决这个问题了。我创建了一个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

我的登录组件保持不变,所以没有变化,我添加到子页面的“基本”用户,所以当我可以使用“标签”布局,以导航之间的这2页。
所以这里是我的“基本”页面,请注意,其他页面有简单的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;

相关问题