首页 > 解决方案 > 如何在客户端 PrivateRoute 上授权 jwt 令牌?

问题描述

我正在尝试向后端的授权端点发送一个简单的 GET 请求,以验证成功验证(登录)后保存在 localStorage 中的用户 jwt 令牌。我已经设置了一个辅助函数(见下文),它将使用标头中的 jwt 令牌向服务器发出 GET 请求,如果收到状态 200,则我返回 true(已授权)。

这个辅助函数在我的 PrivateRoute HOC 中调用,如果为 true 将返回私有组件,如果为 false 将重定向到 Login。

基本上,如果我只是解码令牌并验证令牌是否存在并且因此用户已登录,则基本上我可以使受保护的路由正常工作。但是,这不是根据服务器 jwt verify 检查令牌,因此不安全。我不确定是否需要在任何地方实现异步代码?我知道验证步骤必须存在于后端,并且前端应该简单地发送带有标头中令牌的 API 请求,但我似乎无法使用 Private Routes 完成这项工作。

我还尝试在主 App 组件上运行辅助函数作为 useEffect 挂钩,并根据辅助函数是否返回授权更新“loggedIn”状态变量,然后可以将其作为道具传递给其他组件。我已经为我的导航栏组件执行此操作,以根据“登录”状态是真还是假来显示“登录/注册”或“注销”。

服务器端验证令牌功能:

const jwt = require('jsonwebtoken');

module.exports = function (req, res, next) {
    const token = req.header('auth-token');
    if(!token) return res.status(401).send('Access Denied');

    try {
        const decoded = jwt.verify(token, process.env.TOKEN_SECRET);
        req.user = decoded;
        next();
    } catch (err) {
        res.status(400).send('Invalid Token');
    }
}

辅助功能:

import React from 'react';
    import { Redirect } from 'react-router-dom';
    import axios from 'axios';

    const checkAuth = {
        isAuthorised: false,
        userID: void (0),

        authorise: function () {

            const token = window.localStorage.getItem("access_token");
            if (!token) {
                console.log('Invalid Token');
                window.localStorage.setItem("access_token", null);
            }

            const response = axios({
                method: 'get',
                url: 'http://localhost:3001/api/verify',
                headers: { 'auth-token': token }
            })
            const data = response.data;
            if (response.status === 200) {
                this.isAuthorised = true;
                this.userID = data._id;
                return this.isAuthorised;
            }

            return this.isAuthorised;
        },
        logout: function () {
            window.localStorage.setItem("access_token", null);
            this.isAuthorised = false;
            return <Redirect to="/" />
        },
    }

    export default checkAuth;

私人路线 HOC:

    import React from 'react';
    import { Redirect, Route } from 'react-router-dom';
    import checkAuth from './helper';

    const PrivateRoute = ({ component: Component, path, ...rest }) => {

    return (
        <Route
            path={path}
            {...rest}
            render={props =>
                checkAuth.authorise()
                    ? <Component {...props} />
                    : <Redirect to={{ pathname: "/login" }} />
            }
        />
    );
        }
    export default PrivateRoute;

应用组件(隐藏导入):

const App = () => {
  const [loggedIn, setLoggedIn] = useState(false);
  const [user, setUser] = useState();

  const verify = () => {
    if (checkAuth.authorise()) {
      setLoggedIn(true);
      setUser(checkAuth.userID);
      console.log('logged in');
      return true;
    } else {
      console.log('not logged in');
      return false;
    }
  }

  useEffect(() => {
    verify()
  }, [loggedIn]);

  return (
    <div>
      <NavBar loggedIn={loggedIn} user={user} />
      <Router>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/story" component={Story} />
          <Route path="/prologue" component={Prologue} />
          <PrivateRoute path="/chapters" component={Chapters} />
          <PrivateRoute path="/forum" component={Forum} />
          <PrivateRoute path="/characters" component={Characters} />
          <PrivateRoute path="/characters/:id" component={CharacterSheet} />
          <Route path="/login" component={Login} />
          <Route path="/register" component={Register} />
          <Route component={NoMatch} />
        </Switch>
      </Router>
    </div>
  );
}

export default App;

NavBar 组件(仅相关部分):

const NavBar = ({ loggedIn, user }) => {
    return (
      ...
          {loggedIn ? (
                    <Nav>
                        <h1 style={{ paddingRight: '30px', color: 'black', fontSize: '32pt'}}>Welcome, {user}</h1>
                        <Nav.Link href="/" onClick={() => checkAuth.logout()}>Sign Out</Nav.Link>
                    </Nav>
                ) : (
                        <Nav>
                            <Nav.Link href="/login">Login</Nav.Link>
                            <Nav.Link href="/register">Register</Nav.Link>
                        </Nav>
                    )}
                ...
    )
}

export default NavBar;

我希望当用户登录时,NavBar 会根据 useEffect 钩子调用的授权帮助函数更新以显示用户登录。

我还希望当用户路由到受保护的路由之一(PrivateRoute)时,辅助函数将运行并返回 true(登录后)并随后呈现。

当使用以下代码代替辅助函数中的 API 调用时,我得到了预期的结果(但据我了解,这不是正确的授权):

    authorise: function () {

        const token = window.localStorage.getItem("access_token");
        if (!token) {
            console.log('Invalid Token');
            window.localStorage.setItem("access_token", "");
        }

        try {
            const decoded = decode(token);
            console.log(decoded)
            if (decoded) {
                this.isAuthorised = true;
                this.userID = decoded._id;
                return this.isAuthorised;
            }
        }
        catch {
            console.log('invalid token')
        }

        return this.isAuthorised;
    },

目前,登录似乎什么也没做——尽管用户令牌被接收并存储在 LocalStorage 中。

标签: reactjsasync-awaitreact-routerjwtauthorization

解决方案


axios 返回一个承诺,因此,基本上,您需要将验证代码更改为以下内容:

return axios({
    method: 'get',
    url: 'http://localhost:3001/api/verify',
    headers: { 'auth-token': token }
})
    .then(response => {
        const data = response.data;
        if (response.status === 200) {
            this.isAuthorised = true;
            this.userID = data._id;
        }

        return this.isAuthorised;
    });

然后也将checkAuth.authorise()呼叫作为承诺处理


推荐阅读