如何在中间件中访问 (req , res)?

Posted

技术标签:

【中文标题】如何在中间件中访问 (req , res)?【英文标题】:How can I access (req , res) inside a middleware? 【发布时间】:2021-10-07 23:19:42 【问题描述】:

这是我的授权中间件,我在路由中这样使用它:

router.get('/dashboard', authorize(), dashboard);

所以如您所见,只有授权用户才能访问仪表板吗?

这里是函数:

const jwt = require('express-jwt');

module.exports = authorize;

function authorize(roles = []) 
    // roles param can be a single role string (e.g. Role.User or 'User') 
    // or an array of roles (e.g. [Role.Admin, Role.User] or ['Admin', 'User'])
    if (typeof roles === 'string') 
        roles = [roles];
    

    return [
        // authenticate JWT token and attach user to request object (req.user)
        jwt( secret, algorithms: ['HS256'] ),

        // authorize based on user role
        async (req, res, next) => 
            const account = await db.Account.findById(req.user.id);
            const refreshTokens = await db.RefreshToken.find( account: account.id );

            if (!account || (roles.length && !roles.includes(account.role))) 
                // account no longer exists or role not authorized
                return res.status(401).json( message: 'Unauthorized' );
            

            // authentication and authorization successful
            req.user.role = account.role;
            req.user.ownsToken = token => !!refreshTokens.find(x => x.token === token);
            next();
        
    ];

这是对函数具体作用的解释(您现在可以跳过它):

可以将授权中间件添加到任何路由,以限制具有指定角色的经过身份验证的用户访问该路由。如果省略了角色参数(即 authorize()),那么无论角色如何,所有经过身份验证的用户都可以访问该路由。账户控制器使用它来限制对账户 CRUD 路由的访问和撤销令牌路由。

授权函数返回一个包含两个中间件函数的数组:

第一个(jwt( ... )) 通过验证http 请求的“Authorization”标头中的JWT 访问令牌来验证请求。身份验证成功后,用户对象将附加到包含来自 JWT 令牌的数据的 req 对象,在此示例中包括用户 ID (req.user.id)

第二个通过检查经过身份验证的帐户是否仍然存在并根据其角色有权访问请求的路由来授权请求​​。第二个中间件函数还将角色属性和ownsToken 方法附加到req.user 对象,以便控制器函数可以访问它们。 如果身份验证或授权失败,则返回 401 Unauthorized 响应。

这就是我想做的:

我想在触发此中间件时将 jwtToken 添加到授权标头中好吗?

所以我在函数的开头添加了这个:

function authorize(roles = []) 
    // roles param can be a single role string (e.g. Role.User or 'User') 
    // or an array of roles (e.g. [Role.Admin, Role.User] or ['Admin', 'User'])
    if (typeof roles === 'string') 
        roles = [roles];
    

    // This is the code I added
    const jwtToken = req.cookies.jwtToken; // get the jwtToken from cookie
    const token = `Bearer $jwtToken`; // format the token
    res.header('Authorization', 'Bearer '+ token); // simply add the jwtToken to headers


    return [
        // authenticate JWT token and attach user to request object (req.user)
        jwt( secret, algorithms: ['HS256'] ),

   ...

无论我多么努力,我都无法做到这一点,因为我无法访问中间件中的 req 和 res

如何在authorize 中间件中访问reqres

【问题讨论】:

【参考方案1】:

问题是你返回一个数组:

return [
    jwt( secret, algorithms: ['HS256'] ),

    // authorize based on user role
    async (req, res, next) =>  /* ... */ 
]

Express 不知道如何处理它。 Express 需要一个函数作为中间件(你的 async (req,res,next) => ...)。

解决此问题的一种方法是使用扩展运算符将 jwt 中间件和您的中间件分别传递给 Express:

router.get('/dashboard', ...authorize(), dashboard);

这可行,但其他习惯于 Express 的开发人员并不完全期望。看起来有点奇怪。

现在看起来怪异的代码没有任何问题,但我认为有更好的方法来做到这一点 - 使用路由器链接你的中间件:

const jwtMiddleware = jwt( secret, algorithms: ['HS256'] );

function authorize(roles = []) 
    // roles param can be a single role string (e.g. Role.User or 'User') 
    // or an array of roles (e.g. [Role.Admin, Role.User] or ['Admin', 'User'])
    if (typeof roles === 'string') 
        roles = [roles];
    

    let midRouter = express.Router();

    midRouter.use(jwtMiddleware);
    midRouter.use(async (req, res, next) => 
          const account = await db.Account.findById(req.user.id);
          const refreshTokens = await db.RefreshToken.find( account: account.id );

          if (!account || (roles.length && !roles.includes(account.role))) 
              // account no longer exists or role not authorized
              return res.status(401).json( message: 'Unauthorized' );
          

          // authentication and authorization successful
          req.user.role = account.role;
          req.user.ownsToken = token => !!refreshTokens.find(x => x.token === token);
         // authorize based on user role
         next();
    );

    return midRouter;

现在您可以使用您的原始代码:

router.get('/dashboard', authorize(), dashboard);

就我个人而言,我不会为此烦恼,只需使用 express-jwt 记录的使用方式即可:

router.use(jwt( secret, algorithms: ['HS256']));
router.get('/dashboard', authorize(), dashboard);

这是大多数 Express 开发人员希望看到的,您可以从 authorize() 函数中删除 jwt 部分。

请记住,您可以在其.unless() 方法中定义您不希望jwt 身份验证发生的路径列表。

【讨论】:

"并且 Express 不知道如何处理它。" - 是的,它确实。看the API documentation for use回调函数;可以是:一个中间件函数,一系列中间件函数(以逗号分隔,中间件函数数组。,以上所有的组合【参考方案2】:

authorize 不是中间件。它执行中间件,因此您只需将代码从authorize 移至(req, res, next) => 函数并返回即可。

【讨论】:

它不能去那里:OP想要在jwt( secret, algorithms: ['HS256'] ),中间件之前运行他们的代码,而不是在它之后的中间件中。【参考方案3】:

如何用你的方法解决这个问题

authorize 函数是不是中间件。

它是一个函数,当您调用它时,会返回 两个 中间件函数(在一个数组中)。

第一个中间件函数的返回值是:

// authenticate JWT token and attach user to request object (req.user)
jwt( secret, algorithms: ['HS256'] ),

它在req 对象上创建user 属性。

第二个中间件函数是这个:

// authorize based on user role
async (req, res, next) => 
    // etc

要访问req 对象,请在该函数中

但是,您似乎希望jwt( secret, algorithms: ['HS256'] ), 返回的中间件运行之前访问它。

为此,您需要在数组的front添加一个第三个中间件函数。

return [
    (req, res, next) => 
        // access cookies and transfer to header variable here
        next();
    ,

    // authenticate JWT token and attach user to request object (req.user)
    jwt( secret, algorithms: ['HS256'] ),

正确的做法

看the documentation for the module you are using:

自定义令牌位置

用于从请求中提取令牌的自定义函数可以是 使用 getToken 选项指定。如果您需要通过,这很有用 通过查询参数或 cookie 获取令牌。你可以扔一个 此函数出错,将由 express-jwt 处理。

app.use(jwt(
  secret: 'hello world !',
  credentialsRequired: false,
  getToken: function fromHeaderOrQuerystring (req) 
    if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') 
        return req.headers.authorization.split(' ')[1];
     else if (req.query && req.query.token) 
      return req.query.token;
    
    return null;
  
));

因此您可以传递一个直接读取 cookie 的 getToken 函数,而不是摆弄尝试重写请求。

【讨论】:

以上是关于如何在中间件中访问 (req , res)?的主要内容,如果未能解决你的问题,请参考以下文章

Express 中间件中的 req.locals vs. res.locals vs. res.data vs. req.data vs. app.locals

node路由访问,中间件返回数据

如何从路由器访问应用程序级中间件?

express中间件

expressjs可以动态设置静态目录吗

node框架