React 中的 JWT 安全路由

JWT Secure Routes in React

我正在将 JWT 身份验证添加到我正在开发的博客应用程序中。在服务器端(使用 Nodejs 构建)我正在创建令牌并在成功登录后将其发回。在客户端,我将令牌保存在 LocalStorage 中。当我登录并检查开发工具中的应用程序选项卡时,我可以看到令牌。在博客被 post 编辑到的服务器路由上,我检查身份验证。如果令牌已通过博客 post 到数据库的身份验证,但如果我删除令牌或更改它然后发出 post 请求,请求将失败,正如预期的那样。

到目前为止一切顺利。

我很困惑的是如何在客户端限制访问博客编辑器所在的页面。如果人们没有通过身份验证,他们根本不应该能够访问此页面,即使没有通过身份验证他们也不能 post。

服务器上的登录路径:

router.post('/login', async (req, res, next) => {
    const cursor = User.collection.find({username: req.body.username}, {username: 1, _id: 1, password: 1});
    if(!(await cursor.hasNext())) {
        return res.status(401).json({ message: 'Cannot find user with that username' });
    }
    const user = await cursor.next();
    try {
    if(await bcrypt.compare(req.body.password, user.password)) {
        const token = jwt.sign({
            email: user.email,
            userId: user._id
        }, process.env.JWT_SECRET, { expiresIn: "1h" })
        return res.status(201).json({
            message: 'User Authenticated',
            token: token
        });
    } else {
        return res.status(400).json({ 
            authenticated: false,
            username: req.body.username,
            password: req.body.password
        })
    }
    } catch (err) {
        return res.status(500).json({ message: err })
    }
});

我如何检查服务器上的令牌身份验证:

const jwt = require('jsonwebtoken');

module.exports = (req, res, next) => {
    try {
        const token = req.headers.authorization;
        console.log(token);
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.userData = decoded;
        next();
    } catch (error) {
        return res.status(401).json({ message: 'Auth Failed' })
    }

}

我的客户端登录路由获取:

handleSubmit(event) {
        event.preventDefault();
        const formData = {
            username: event.target.username.value,
            password: event.target.password.value
        }
        fetch('http://localhost:4000/user/login', {
            method: "POST",
            mode: "cors",
            body: JSON.stringify(formData),
            headers: {
                "Content-Type": "application/json"
            }
        })
        .then(res => res.json())
        .then(res => {
            localStorage.setItem('authorization', res.token);
            console.log(res);
        })
        .catch(err => console.error(err)) 
    }

这是我在博客 post编辑器所在的路由上从客户端获取的调用:

handleSubmit = (event) => {
      event.preventDefault();
      const data = new FormData(event.target);
      const body = event.target.postBody.value;
      const postTitle = event.target.title.value;

      console.log(event.target);
      console.log(data);
      console.log(event.target.postBody.value);

      fetch('http://localhost:4000/blog', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          "Authorization": localStorage.getItem('authorization')
        },
        mode: 'cors',
        body: JSON.stringify({
          title: postTitle,
          postBody: body
        })
      })
      .then(res => res.json())
      .then(err => console.error(err))
    }

所以,就像我说的,一切都按预期工作,但我不希望人们在未经身份验证的情况下能够访问编辑器页面。我想我会检查本地存储中是否存在令牌然后重定向?但是我是否还需要检查客户端上的令牌是否也可以在服务器上进行身份验证?那么,每当有人导航到该页面或我想限制访问的任何其他页面时,我是否基本上需要 post 到服务器进行检查?想想看,如果用户已经通过身份验证,我也不希望他们能够访问登录页面。

我听说人们使用 Redux 跨组件管理状态,但我真的不想走那条路,至少现在还不想,因为这个项目是为了学习目的,我真的不想从 Redux 或其他类似的东西开始,直到我自己更好地掌握 React。我不知道我是否需要 Redux,据我了解,这足以知道我可能不需要它。

这与我在 PHP 会议中习惯的流程完全不同,我在思考它时遇到了一些麻烦。

我知道你们可能真的不需要看到所有这些代码,但我也希望一些更有经验的人看到它并指出我可能犯的任何错误或我可以在这里改进的地方。

这就是我目前想出的方法,如果有人知道更好的方法,我绝对愿意接受建议。

我创建了一个名为 CheckAuth 的 class,它实际上只是向服务器发出 GET 请求并随它一起发送 jwt。

checkAuth.js:

class CheckAuth {
    constructor() {
        this.auth = false;
    }

    async checkLogin() {
        console.log(localStorage.getItem("authorization"));
        let data = await fetch('http://localhost:4000/auth', {
            method: "GET",
            mode: "cors",
            headers: {
                "Content-Type": "application/json",
                "authorization": localStorage.getItem("authorization")
            }
        })
        return data.json();

    }

    logout(cb) {
        localStorage.removeItem('authenticated')
        this.auth = false;
        cb();
    }

    async isAuthenticated() {
        const data = await this.checkLogin()
        return data;
    }

}

export default new CheckAuth();

然后在只有登录用户应该看到的页面上,我正在做一个简单的检查,看看他们是否有令牌,以及它在 componentDidMount() 中是否有效。

componentDidMount() {
        const check = checkAuth.isAuthenticated();
        console.log(check);
        check.then(res => {
            console.log(res);
            if(res.authenticated !== true) {
                this.props.history.push("/login");
            }
        })
        .catch(err => { console.error(err) })
    }