如何将 jwt 令牌发送到 node.js 中的受保护路由

Posted

技术标签:

【中文标题】如何将 jwt 令牌发送到 node.js 中的受保护路由【英文标题】:How to send jwt token to protected route in node.js 【发布时间】:2020-08-21 15:04:33 【问题描述】:

我创建了一个登录表单,如果用户输入正确的密码和用户名,该表单应将用户重定向到仪表板页面。如果用户在未登录的情况下尝试导航到仪表板 URL,则不应显示该页面,因为它是受保护的路由。我试图在用户登录时发送 jwt 令牌,但这不起作用我登录时只收到禁止消息,所以似乎令牌未正确发送,我该如何发送 jwt 令牌并访问用户登录成功后的受保护路由?

这是我的 server.js:

const express = require('express');
const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
let Post = require('./models/post.model.js');
const app = express();
const cors = require('cors');
require('dotenv').config();

app.use(cors());
app.use("/assets", express.static(__dirname + "/assets"));
app.use(bodyParser.urlencoded( extended: true ));
const BASE_URL = process.env.BASE_URL;

const PORT = process.env.PORT || 1337;
mongoose.connect(BASE_URL,  useNewUrlParser: true, useUnifiedTopology: true )

const connection = mongoose.connection;

connection.once('open', function () 
    console.log('Connection to MongoDB established succesfully!');
);

app.set('view-engine', 'ejs');

app.get('/', (req, res) => 
    res.render('index.ejs');
);

app.post('/', (req, res) => 
    let username = req.body.username;
    let password = req.body.password;

    const user = 
        username: username,
        password: password
    

    jwt.sign( user , process.env.SECRET_KEY, (err, token) => 
        res.json(
            token
        )
    );

    if (username !== process.env.USER_NAME && password !== process.env.USER_PASSWORD) 
        res.json('Invalid credentials');
     else 
        res.setHeader('Authorization', 'Bearer '+ token);
        res.redirect('/dashboard')
    

);

app.get('/dashboard', verifyToken, (req, res) => 
    jwt.verify(req.token, process.env.SECRET_KEY, (err, authData) => 
        if (err) 
            res.sendStatus(403);
         else 
            res.sendStatus(200);
        
    );
    res.render('dashboard.ejs');
);

app.get('/dashboard/createPost', verifyToken, (req, res) => 
    res.render('post.ejs');
);

app.post('/dashboard/createPost', async (req, res) => 
    let collection = connection.collection(process.env.POSTS_WITH_TAGS);
    res.setHeader('Content-Type', 'application/json');
    let post = new Post(req.body);
    collection.insertOne(post)
        .then(post => 
            res.redirect('/dashboard')
        )
        .catch(err => 
            res.status(400).send(err);
        );
);

// TOKEN FORMAT
// Authorization: Bearer <access_token>

//Verifing the Token
function verifyToken(req, res, next) 
    // Get auth header value
    const bearerHeader = req.headers['authorization'];
    // Check if bearer is undefined
    if (typeof bearerHeader !== 'undefined') 
        // Spliting the bearer
        const bearer = bearerHeader.split(' ');
        // Get token from array
        const bearerToken = bearer[1];
        // Set the token
        req.token = bearerToken;
        // Next middleware
        next();

     else 
        // Forbid the route
        res.sendStatus(403);
    



app.listen(PORT);

【问题讨论】:

这可能有助于***.com/questions/59241078/… 用户登录时如何将令牌发送到服务器? 您希望令牌位于标头中,但您在前端 javascript 中执行任何操作以在标头中发送接收到的令牌? 我错误地上传了旧代码,我现在更新了我的问题,当用户密码在 else 语句中匹配时,我尝试发送它 res.setHeader('Authorization', 'Bearer'+ token) ; 您能否也发布您的前端代码,从您将 jwt 发送到后端的位置 【参考方案1】:

看这个例子,我使用中间件(checkAuthLogin),这段代码包含了你问题的所有内容:

index.js:

const express = require('express');
const app = express();
require('./db/mongoose');

const userRouter = require('./routers/user');


app.use(express.json());
app.use(userRouter);


app.listen(3000, ()=>  
    console.log('Server is up on port ', 3000)
);

db/mongoose.js:

const mongoose = require('mongoose');

mongoose.connect("mongodb://127.0.0.1:27017/db-test" 
    useNewUrlParser : true,
    useCreateIndex : true,
    useFindAndModify : false,
    useUnifiedTopology: true
);

路由器/user.js:

const express = require('express');
const router = new express.Router();
const RootUser = require('../models/root-user');
const checkRootLogin = require('../middleware/checkAuthLogin');

router.post('/createrootuser', async (req, res) => 

    const updates = Object.keys(req.body);
    const allowedUpdatesArray = ['name', 'password'];
    const isValidOperation = updates.every((update) => allowedUpdatesArray.includes(update));

    if (!isValidOperation) 
        return res.status(400).send(error: 'Invalid Request Body')
    

    const rootUser = new RootUser(req.body);

    try 
        await rootUser.save();
        // sendWelcomeEmail(user.email, user.name)
        const token = await rootUser.generateAuthToken();
        //console.log(user)
        res.status(201).send(rootUser, token);
     catch (e) 
        res.status(400).send(e)
    

);

//use this middleware(checkRootLogin) for check root user can access this function
router.post('/rootconfig', checkRootLogin, async (req, res) => 

        res.status(200).send(success: 'success add root config')

);

module.exports = router;

模型/root-user.js:

const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');    

const userRootSchema = new mongoose.Schema(
    name: 
        type : String,
        required: true,
        unique : true,
        trim : true,
        lowercase : true,
    ,
    password: 
        type : String,
        required: true,
        unique : true,
        trim : true,
        lowercase : true,
        minlength : 6,
        validate (value) 
            //if (validator.contains(value.toLowerCase(), 'password'))
            if (value.toLowerCase().includes('password'))
                throw new Error('Password can not contained "password"')
            
        
    ,

    tokens : [
        token : 
            type : String ,
            required : true
        
    ],

, 
    timestamps: true
);


userRootSchema.methods.generateAuthToken = async function()

    const root = this;
    // generate token
    try 
        // const token = jwt.sign( _id : user._id.toString(), process.env.JWT_SECRET);
        const token = jwt.sign( _id : root._id.toString(), "test");
        // add token to user model
        root.tokens = root.tokens.concat( token );
        await root.save();
        return token
     catch (e)
        throw new Error(e)
    

;



userRootSchema.pre('save', async function(next)
    // this give ccess to individual user
    const user = this;

    if (user.isModified('password'))
        user.password = await bcrypt.hash(user.password, 8)
    
    next()

);

const UserRoot = mongoose.model('UserRoot', userRootSchema);

module.exports = UserRoot;

中间件/checkAuthLogin.js:

const jwt = require('jsonwebtoken');
const RootUser = require('../models/root-user');  

const checkRootLogin = async (req, res, next) => 
    try 
        const token = req.header('Authorization').replace('Bearer ', '');
        // const decoded = jwt.verify(token, process.env.JWT_SECRET);
        const decoded = jwt.verify(token, "test");

        const rootUser = await RootUser.findOne(_id: decoded._id, 'tokens.token': token);

        if (!rootUser) 
            throw new Error("User cannot find!!");
        

        req.token = token;
        req.rootUser = rootUser;
        req.userID = rootUser._id;
        next()
     catch (e) 
        res.status(401).send(error: 'Authentication problem!!')
    
;

module.exports = checkRootLogin;

【讨论】:

【参考方案2】:

您的问题是您的token 变量只能在jwt.sign 调用的回调内部访问,所以当您尝试在res.setHeader('Authorization', 'Bearer '+ token); 执行此操作时,它不会知道您指的是哪个变量,因此出现未定义的错误。顺便说一句,如果您要异步使用jwt.sign,那么使用它的代码也需要在回调内部,否则回调外部的同步代码可能会首先执行(因此无法访问异步代码的任何结果),因为异步回调在后台执行。这里的解决方案是将您的使用切换为同步使用,或者将您的响应代码放在回调中。此外,调用res.json 将结束响应,所以我不确定您究竟想通过多个响应调用来完成什么

同步版本:

app.post('/', (req, res) => 
    let username = req.body.username;
    let password = req.body.password;

    const user = 
        username: username,
        password: password
    ;

    let token = undefined;
    try 
        token = jwt.sign( user , process.env.SECRET_KEY);
     catch (e) 
        // handle error
    

    if (username !== process.env.USER_NAME && password !== process.env.USER_PASSWORD) 
        res.json('Invalid credentials');
     else 
        res.setHeader('Authorization', 'Bearer '+ token);
        res.redirect('/dashboard');
    

);

异步版本:

app.post('/', (req, res) => 
    let username = req.body.username;
    let password = req.body.password;

    const user = 
        username: username,
        password: password
    

    jwt.sign( user , process.env.SECRET_KEY, (err, token) => 
        if (username !== process.env.USER_NAME && password !== process.env.USER_PASSWORD) 
            res.json('Invalid credentials');
         else 
            res.setHeader('Authorization', 'Bearer '+ token);
            res.redirect('/dashboard')
        
    );

);

在这些示例中,我删除了res.json( token ),因为您不能使用res.json 然后执行重定向,而是修改这些部分以最适合您的代码。另一方面,您可能不想在令牌中包含密码,因为虽然 JWT(当使用不包括加密的默认/标准算法时)在加密上保证是不可修改的,但它们仍然可读 em>

【讨论】:

【参考方案3】:

我有一种发送 jwt 令牌的解决方案,但您需要再安装一个包。如果您认为值得,也许您可​​以关注。

我只将 express 用于后端 api。但您可以将此处应用的相同逻辑应用于您的应用程序。

您需要安装的库是express-jwt

它处理路由以阻止对需要身份验证的端点的访问。

server.js

require('dotenv').config()
const express = require('express');
const logger = require('morgan');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const expressJwt = require('express-jwt');

const app = express();

cors( credentials: true, origin: true );
app.use(cors());
app.use(express.json());
app.use(express.urlencoded( extended: true ));
app.use('/secure', expressJwt( secret: process.env.SECRET ));
app.use(require('./server/index'));

app.get('/secure/dashboard') => 
    //now you can only access this route with authorization header
    //prependending the '/secure/ to new routes should make them return 401 when accessed without authorization token
    //accessing this route without returns 401.
    //there is no need to validate because express-jwt is handling.
    console.log(res.user)//should print current user and pass signed with token
    res.render('dashboard.ejs');
);

app.post('/', (req, res) => 
    let username = req.body.username;
    let password = req.body.password;
    //jwt.sign( user , process.env.SECRET_KEY, (err, token) => 
    //    res.json(
    //        token
    //    )
    //);
    //shouldn't sign json here, because there is no guarantee this is a valid
    //username and password it can be an impostor

    if (username !== process.env.USER_NAME && password !== process.env.USER_PASSWORD) 
        res.json('Invalid credentials');
     else 
        const user = 
           username: username,
           password: password
        ;
        const tk = ;
        tk.token = 'Bearer ' + jwt.sign(user, process.env.SECRET_KEY,  expiresIn: 1800 );//expires in 1800 seconds
        res.status(200).json(tk);
    
);

现在在您的前端中,将此路由发送的授权令牌放入 cookie 中或存储在客户端中。 使用安全仪表板路由的标头授权执行下一个请求。

【讨论】:

【参考方案4】:

我认为是登录控制器功能的问题

在尝试向他发送令牌之前,您必须先检查用户是否拥有正确的密码

您应该将jwt sign function 的结果保存在一个变量中,以便在用户拥有正确凭据时发回给他。

再次发送密码给用户是没有意义的,只需要用户名

你可以试试这个:


app.post('/', (req, res) => 

    const username , password = req.body;

    if (username !== process.env.USER_NAME && password !== process.env.USER_PASSWORD) 
        return res.json('Invalid credentials');
    


    const token = jwt.sign(username:username , SECRET)
    res.setHeader('Authorization', token);
    res.redirect('/dashboard')

);


【讨论】:

以上是关于如何将 jwt 令牌发送到 node.js 中的受保护路由的主要内容,如果未能解决你的问题,请参考以下文章

快速中间件中的 Node.js JWT 刷新令牌

Node.js:登录后在 URL 中发送带有令牌(JWT)的新请求

如何从 Node.js 中的 jwt 令牌获取 user.id?

如何在 Node JS 中使用 jwt 授权具有不同令牌的所有用户

如何延长node.js中相同jwt令牌的到期时间

如何解码 Java 生成的 Node.js 中的 jwt 令牌?