NodeJS React. Backend中的路由保护无法处理JWT令牌

33qvvth1  于 2023-03-17  发布在  Node.js
关注(0)|答案(1)|浏览(111)

我做了这个项目专门用来练习认证和安全。但是看在上帝的份上,我不能让这个东西工作。我正在使用Node.js mongoDB后端和react前端。到目前为止,我已经设法从服务器发送了一个Jwt令牌,并将其存储在浏览器的本地存储中。但是现在我坚持将这个令牌发送到后端进行验证,并将用户导航到“/”如果他/她在登录之前尝试访问'/messages'路由,则会打开“/messages”页面。下面是代码。我使用axios来处理http请求,使用react-router-dom来处理路由。
App.jsx

import { Routes, Route } from 'react-router-dom'
import Login from './pages/Login'
import Register from './pages/Register'
import Messages from './pages/Messages'

function App() {
  return (
    <Routes>
      <Route path='/messages' element={<Messages />}/>
      <Route path='/register' element={<Register />} />
      <Route index element={<Login />} />
    </Routes>
  )
}

export default App

Messages.jsx

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

const Messages = () => {

    const navigate = useNavigate();

    const [message, setMessage] = useState("");
    const [messages, setMessages] = useState([]);
    const [emptyErrorMessage, setemptyErrorMessage] = useState(false);

    const postMessage = async e => {
        e.preventDefault();

        if (!message) setemptyErrorMessage(true);
        else {
            await axios.post('http://localhost:5000/messages', {message});
            window.location.reload(false);
        }

    } 
    
    const getMessages = async () => {

        try {
            const token = localStorage.getItem('token');
            const res = await axios.get('http://localhost:5000/messages', {
                headers : {
                    'Authorization': token
                }
            });
            setMessages(res.data);
        } catch (err) {
            console.log(err);
            if (err.response.status == 401) {
                navigate('/');
            }
        }

    }

    useEffect(() => {
       getMessages();
    }, []);

  return (
    <div>
        <form>
            <label>Message:</label><br />
            <input type="text" onChange={e => setMessage(e.target.value)} name='message' autoComplete='off'/>
            {emptyErrorMessage ? <h5>This Field is required</h5> : null}

            <button type='submit' onClick={postMessage}>Submit</button>
        </form>

        {messages && messages.length > 0 ? messages.map(singleMessage => {
            return <h4 key={singleMessage._id}>{singleMessage.message}</h4>
        }) : <h1>No messages Yet</h1>}
    </div>
  )
}

export default Messages

Login.jsx

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

const Login = () => {

    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');

    const navigate = useNavigate();

    const postLoginData = async () => {
        const res = await axios.post('http://localhost:5000/auth/login', {
            email,
            password
        });

        if (res.data.status == 200) {
            const token = res.data.token;
            localStorage.setItem('token', token);
            axios.defaults.headers.common['Authorization'] = `${token}`;
            navigate('/messages')
        } else window.location.reload(false);
    }   

  return (
    <>
        <div className="login-form">

            <form>

                <label>Email:</label>
                <input type="email" onChange={e => setEmail(e.target.value)} name='email' />

                <label>Password:</label>
                <input type="password" onChange={e => setPassword(e.target.value)} name='password' />
                
            </form>

            <button onClick={postLoginData}>Login</button>

            <br />
            <a href="/register">Or register</a>

        </div>
    </>
    )
}

export default Login

这就是前端。这里是后端控制器。
userRoutes.js:

import express from 'express';
import { loginUser, registerUser } from '../controllers/userController.js';

const router = express.Router();

router.post('/login', loginUser)
router.post('/register', registerUser)

export default router;

messageRoutes.js:

import express  from "express";
import { postMessage, getMessage } from "../controllers/messageControllers.js";
import verifyToken from '../middleware/verifyToken.js';

const router = express.Router();

router.post('/', postMessage);
router.get('/', verifyToken, getMessage);

export default router;

verifyToken.js:

import jwt from 'jsonwebtoken';

const verifyToken = (req, res, next) => {
    const authHeader = req.headers.Authorization;

    if (!authHeader) return res.status(401).json({ status: 401 });

    try {
        const token = authHeader.split(" ")[1];
        const decoded = jwt.verify(token, `${process.env.JWT_SECRET}`)
    
        console.log(decoded);
        next()
    } catch (err) {
        console.log(err);
    }

}

export default verifyToken;

messagesController.js

import Message from '../models/messageSchema.js'

export const postMessage = async (req, res) => {
    const {message} = req.body;

    const newMessage = new Message ({
        message: message
    });

    await newMessage.save();
    res
    .status(200)
    .json({ status: 200 });
}

export const getMessage = async (req, res) => {
    const messages = await Message.find({}).exec();
    res.send(messages);
}

userController.js

import User from "../models/userSchema.js";
import bcrypt from 'bcrypt';
import generateToken from '../middleware/generateToken.js'

export const loginUser = async (req, res) => {
    const {email, password} = req.body;

    try {
        if (!email || !password) throw Error('All fields must be filled')

        const potentialUser = await User.findOne({ email });

        if (!potentialUser) throw Error("Invalid Login Credentials");

        const match = await bcrypt.compare(password, potentialUser.password)
        if (!match) throw Error("Invalid Login Credentials");

        const token = generateToken(potentialUser._id);

        res
        .status(200)
        .json({
            email,
            token,
            status: 200
        })
    } catch (err) {
        res
        .status(400)
        .json({ err: err.message })
    }
}

export const registerUser = async (req, res) => {
    const {email, password} = req.body;

    try {
        if (!email || !password) throw Error('All fields must be filled')

        const userExists = await User.findOne({ email });

        if (userExists) {
            throw Error('User Already Exists');
        }

        const salt = await bcrypt.genSalt(10);
        const hashedPassword = await bcrypt.hash(password, salt);

        const user = new User ({
            email,
            password: hashedPassword
        })

        const token = generateToken(user._id);
        await user.save();

        res
        .status(200)
        .json({ status: 200, token });

    } catch(err) {
        res
        .status(400)
        .json({ message: err.message })
    }

}

关于路线的一些背景:路由的端点在主服务器文件中定义,我没有提供,因为我相信错误不是来自那里。访问/login或/register。完整路径是/auth/login,注册也是如此。在前端,“/”是登录路由,“/register”是注册路由。这两个路由都是公共的。“/messages”路由应该受到保护。其实不是。
预期行为:如果用户使用正确的用户名和密码登录,则会生成一个令牌并存储在浏览器的本地存储中,用户将被导航到“/messages”。但如果有人试图在未登录的情况下访问“/messages”,则用户应被重定向到登录页面。
实际行为:使用我提供的代码,无论用户名和密码是否匹配,我都会被重定向到登录页面。这很明显,因为在这一点上我已经对代码做了很多改动。另外,当我将令牌作为授权头发送并尝试在后端控制台记录它时,结果发现没有这样的授权头。
注意:提供的代码可能不完全有意义。请考虑到Messages.jsx和VerifyToken函数中处理数据的方式可能有很多不规则之处。另外,如何在axios中使用授权头来处理请求以及如何验证令牌也会很棒。
我认为问题可能存在于Messages.jsx文件和Verify Token函数中,尽管它也可能是用户控制器之一。

e4yzc0pl

e4yzc0pl1#

我没有足够的评分来发表评论,但是看起来您的verifyToken函数立即希望Authorization头中包含两个单词,中间用空格隔开,第二个单词是实际的标记:const token = authHeader.split(" ")[1];,但看起来您只在axios请求中发送令牌本身。按照惯例,您通常会有Authorization: Bearer xxx,其中xxx是您的令牌。我认为在axios请求中的令牌之前添加Bearer应该可以解决此问题。

相关问题