Express.js 中间件为上面定义的路由执行

Posted

技术标签:

【中文标题】Express.js 中间件为上面定义的路由执行【英文标题】:Express.js middleware executing for a route defined above it 【发布时间】:2018-11-28 23:38:09 【问题描述】:

根据我阅读的here 和here,您放置中间件功能的顺序很重要,因为如果将某些路由放在路由之前,您可以让某些路由不通过中间件功能,并且路由放在后面的会经过这个中间件函数。

我看到的结果好坏参半,因为我的开发环境不尊重这一点,而我的生产环境却是。代码完全一样。

我想要做的是让我的登录路由不受令牌检查器中间件功能的保护,而让我的其余路由受到令牌的保护。

这是我的代码:

routes.get('/login', function(req, res) 
    // login user, get token
);

routes.use(function(req, res, next) 
    // check header or url parameters or post parameters for token
    var token = req.headers['access-token'];
    // decode token
    if (token) 
        // validate token
    
    else if (req.method === 'OPTIONS') 
        next();
    
    else 
        // if there is no token
        // return an error
        return res.status(403).send(
            success: false,
            message: 'No token provided.'
        );
    
);

routes.get('/query/:keywords', function(req, res) 
    console.log(req.params.keywords);
    // execute query
);

app.use('/', routes);

/query 路由是唯一应该通过令牌中间件功能的路由,对吗?现在我正在通过令牌中间件功能获得/login 路由,这没有意义,因为我不需要有令牌才能登录。

更好的是,如果有一种方法可以定位我想要保护哪些路由以及我不想保护哪些路由,这似乎比必须依赖中间件函数放置位置的“顺序”更好。

【问题讨论】:

应用到前一个的路由不应该发生......永远不会。 javascript 在这一点上是程序性的,全局中间件不应该应用于登录路由......您可以提供任何错误输出吗? 我的猜测是,不是/login 请求击中了您的令牌检查中间件。你应该在那个中间件中做一个console.log(req.url),看看它是什么。它很可能类似于对 favicon 资源的请求。除非您在 /login 路由处理程序中调用 next(),否则在匹配 /login 后不会发生进一步的路由。 【参考方案1】:

由于路由顺序和登录路由从不调用下一个对象的事实,令牌中间件的应用不应该发生在登录路由上。如果没有更多信息,我们真的无法解决除此之外发生的事情,但是您可以 try inspecting it in your dev environment with a debugger 中断并查看命中该中间件的请求。

但是,我们可以为您提供一些关于如何尝试和隔离您的 .use 中间件以及如何应用中间件顺序的信息,以便您可以尝试将其与登录路径完全分开,就像您的问题底部一样。


当仅将中间件应用于特定路由时,您应注意 order 和 .use 用于应在告诉 express 继续在也处理请求的路由器中寻找其他中间件之前应回答请求的中间件。如果您只想在几条路线上使用它,则可以像这样显式地将其添加到几条路线中:

router.get('/route', [ middleware1, middleware2, ..., middlewareX])

router.get('/route', middleware1, middleware2, ..., middlewareX)

两种模式都可以。然而,我发现数组模式更可口一些,因为我可以定义很多我想应用的中间件,然后为特定逻辑连接新的中间件,我只需要修改我声明连接的位置以添加更多功能。然而,很少需要这么多中间件,你应该可以使用任何一个。

您还可以使用路由器将该中间件划分为路由子集,并将其作为第一个中间件应用于路由器之前的路由链。

app.use('/user', authentication, userRouter)

或者你可以把它作为第一个带有 .use 的中间件放在路由器内部,以便它处理所有请求。

因此请记住有关中间件使用的一般提示:

中间件应用程序的顺序很重要 应基于路由应用的可选中间件应与其他中间件一起应用,以便仅用于该路由 错误处理中间件必须始终排在最后,并且有四个参数(err、req、res、next) use routers to section .use middleware to specific routes and sets of routes

您可以在expressjs documentation for middleware找到更多相关信息

【讨论】:

感谢您的回复,有没有办法将中间件应用于全球所有功能,但如果您只说 30 条路线中的几条你不需要应用中间件? @peteb .use 将回答所有到达它的请求。我已经澄清了我的答案以反映这一点 @RobertMennell - 我是说你对“全球”中间件的看法是错误的。路由(包括中间件)只是按照它们定义的顺序进行匹配,当一个匹配时,除非匹配的路由显式调用next() 来告诉 Express 继续寻找匹配,否则不会调用进一步的路由。所以,你的答案是错误的。我现在发布了多个 cmets 解释我的反对意见。 您的解决方案不是 OP 问题的首选解决方案。是的,您可以将中间件附加到每条路由,但这不是 OP 在这里尝试做的事情,这也不是他们想要做的事情的首选解决方案。另外,您没有告诉用户您编辑了任何内容,所以我不知道您何时编辑。我没有收到“编辑”通知。 @RobertMennell - 这是很好的信息,但不是 OP 所要求的。我和你讨论完了。我已经对原始问题发表了评论,OP 没有回应我对他们的问题实际上是什么的猜测。我正在等待他们的回应。不要觉得我需要和你进一步讨论这个问题。我根据您的编辑删除了我的否决票,但我认为您的回答实际上并没有告诉他们他们的问题是什么——这只是另一种不那么普遍、不那么推荐的做事方式,不会试图解释他们为什么有问题当前代码。【参考方案2】:

首先,按照ExpressJS中的用法:

多个回调函数可以处理一个路由(确保指定下一个对象)。例如:

app.get('/example/b', function (req, res, next) 
  console.log('the response will be sent by the next function ...')
  next()
, function (req, res) 
  res.send('Hello from B!')
)

您会注意到它的定义与您在 routes.use(yourFunction(...)) 上声明的内容接近。但是,除了遵循您在文档中看到的示例之外,没有真正的理由这样做,但这是一个很好的开始方式。

但是,这是一个脆弱的实现,express 将允许在其 .get() .post() 方法中使用层次结构,这是正确的,但这是特定于用例的,而不是您要寻找的。​​p>

您需要使用双重回调配置来实现您的自定义身份验证过程。这样做:

// You can save this function in a separate file and import it with require() if you want

const tokenCheck = function(req, res, next) 
    // check header or url parameters or post parameters for token
    var token = req.headers['access-token'];
    // decode token
    if (token) 
        // validate token
    
    else if (req.method === 'OPTIONS') 
        next();
    
    else 
        // if there is no token
        // return an error
        return res.status(403).send(
            success: false,
            message: 'No token provided.'
        );
    
);


routes.get('/login', function(req, res) 
    // login user, get token [Unprotected]
);

routes.get('/query/:keywords', tokenCheck, function(req, res) 
    console.log(req.params.keywords);
    // execute query [Protected with tokenCheck]
);

app.use('/', routes);

您可能需要使用上面的代码,但它会引导您朝着正确的方向前进,这样您就可以根据需要指定特定的路线来执行tokenCheck(req, res, next) 函数。

【讨论】:

【参考方案3】:

最简单的方法是使用路由器中间件来确定需要身份验证的路由和不需要身份验证的路由。由于所有路由器都是中间件,我们可以像任何其他中间件一样实现它们。确保我们按照希望评估路由的顺序放置路由器和路由。

在下面的示例中,Express 服务器有 2 个路由器,一个 LoginRouter 和一个 ApiRouter。

LoginRouter - 在接收到POST /login 的请求时生成一个令牌,并将其返回给请求者以供后续在/api 路由中使用。 ApiRouter - 包装所有其他路由器,集中需要全局应用到 /api 下所有路由的中间件。只有经过身份验证的请求才能访问。

只有在 Header 中包含一个令牌并且该令牌是从 LoginRouter 获得时,才能访问 API 路由器。 LoginRouter 不需要身份验证。

通过此设置,您将通过 ApiRouter 上的 .use() 继续将授权中间件之后的路由器添加到 API 路由器。

以下从其他路由器组合路由器的模式非常强大、可扩展且易于维护。

server.js

const express = require('express')
const bodyParser = require('bodyParser')
const ApiRouter = require('./routes/api')
const LoginRouter = require('./routes/login')
const port = process.env.PORT || 1337

const server = express()

server.use(bodyParser.json())

server.use('/login', LoginRouter)
server.use('/api', ApiRouter)

server.listen(port, () => console.log(`Listening on $port`))

LoginRouter - /routes/login.js

const router = require('express').Router()

router.post('/', (req, res) =>     
    // Validate Credentials
    // some validation code...

    // Then create the token for use later in our API
    let token = '...'

    // Response 200 OK with the token in the message body   
    return res.status(200).send(token)
)

module.exports = router

ApiRouter - /routes/api/index.js

const router = require('express').Router()    
const UsersRouter = require('./routes/api/users')

router.use((req, res, next) => 
    let authorizationHeader = req.headers['authorization'] || req.headers['Authorization'] // handle lowercase
    let [, token] = authorizationHeader.split(' ')
    if (!token) 
        return res.sendStatus(403) // Forbidden, you're not logged in
     else 
        // validate the token    
        if (!tokenIsValid) 
            return res.sendStatus(403) // Forbidden, invalid token
         

        // Everything is good, continue to the next middleware
        return next()
    
)

router.use('/users', UsersRouter)

module.exports = router

UsersRouter - /routes/api/users

const router = require('express').Router()

router.get('/', (req, res) => 
    // We only get here if the user is logged in     
    return res.status(200).json(users: [])
)

module.exports = router

【讨论】:

以上是关于Express.js 中间件为上面定义的路由执行的主要内容,如果未能解决你的问题,请参考以下文章

koa 常用中间件

如何仅在丢失的路线上将 Express.js 设置为 404?

在 Express.js 中使用一个路由作为另一个路由的别名

express js 中的 Socket.io:Router.use 需要中间件功能但未定义

Express.js 或 Angular 用于在 MEAN 应用程序中处理路由?

在 express.js 中,req.files 未定义