【问题标题】:Updating of a custom req.session property value does not seem to persist quick enough自定义 req.session 属性值的更新似乎不够快
【发布时间】:2016-05-10 08:06:31
【问题描述】:

我有一些 Express 中间件处理来自客户端应用程序的 GET 请求,以向使用 OAuth2 令牌的单独 API 服务器发出后续请求,我还使用 express-session 来存储这些令牌。

在发出传出请求的中间件中,我添加了处理以应对访问令牌过期的情况(API 服务器发回 403)并请求刷新令牌,之后它将发出相同的原始向 API 服务器发出请求,因此客户端不知道这一切正在发生。然后通过express-session 将检索到的新令牌持久化回会话存储,以供后续请求使用。这些令牌还用于设置授权不记名令牌标头,您将在下面进一步看到。

这是我的 Express 代码中涉及的部分:

routes.controller.js

//Currently handling GET API requests from client
module.exports.fetch = function(req, res) {
  var options = helpers.buildAPIRequestOptions(req);
  helpers.performOutgoingRequest(req, res, options);
};

helpers.js

module.exports.buildAPIRequestOptions = function(req, url) {
  var options = {};
  options.method = req.method;
  options.uri = 'http://someurl.com' + req.path;
  options.qs = req.query;
  options.headers = {
    'Authorization': 'Bearer ' + req.session.accessToken
  };
  return options;
};

module.exports.performOutgoingRequest = function(req, res, options) {
  request(options, function(err, response, body){
    if(response.statusCode === 401){
      console.log(chalk.red('\n--- 401 RESPONSE RECEIVED TRY REFRESHING TOKENS ---'));
      //Note the third param to call below is a callback and is invoked when calling next() in the refreshToken middleware
      authController.refreshToken(req, res, function(){
        console.log(chalk.green('\n--- RETRYING ORIGINAL REQUEST WITH UPDATED ACCESS TOKEN ---'));
        //Re-use original request options, but making sure we update the Authorization header beforehand
        options.headers.Authorization = 'Bearer ' + req.session.accessToken;
        retryOutgoingRequest(res, options);
      });
    } else {
      res.status(response.statusCode).send(body);
    }
  }); 
};

function retryOutgoingRequest(res, options) {
  request(options, function(err, response, body){
    if(err) {
      console.log(err);
    }
    res.status(response.statusCode).send(body);
  });
};

auth.controller.js

module.exports.refreshToken = function(req, res, next) {
  var formData = {
      grant_type: 'refresh_token',
      refresh_token: req.session.refreshToken
    },
    headers = {
      'Authorization' : 'Basic ' + consts.CLIENT_KEY_SECRET_BASE64
    };
  request.post({url:consts.ACCESS_TOKEN_REQUEST_URL, form:formData, headers: headers, rejectUnauthorized: false}, function(err, response, body){
    var responseBody = JSON.parse(body);
    if (response.statusCode === 200) {
      req.session.accessToken = responseBody.access_token;
      req.session.refreshToken = responseBody.refresh_token;
      next();
    } else {
      console.log(chalk.yellow('A problem occurred refreshing tokens, sending 401 HTTP response back to client...'));
      res.status(401).send();
    }
  });
};

上面的大部分工作都很好

当用户首次登录时,会从 API 服务器获取一些额外的用户个人资料信息,然后再将它们带到应用程序的主页。

应用程序中的某些页面也会在页面加载时获取数据,因此需要接受访问令牌检查。

在正常使用期间,因此当用户登录并开始点击页面时,我可以看到令牌在到期时通过express-session 被换出并保存在会话存储中。根据我编写的中间件,新的访问令牌正确地用于后续请求。

我现在遇到了我的中间件无法工作的情况。

假设我在一个在页面加载时加载数据的页面上,假设它是一个订单页面。如果我等到 API 服务器上配置的令牌过期时间过去然后刷新浏览器,客户端应用程序将首先请求用户信息,成功后将请求页面所需的订单数据(使用AngularJS 承诺)

在我的 Express 应用程序中,用户信息请求从 API 服务器获取 403,因此令牌通过上面的中间件刷新,req.session.accessToken 得到更新,我可以通过控制台登录我的服务器应用程序看到。但是下一次获取订单数据最终会使用之前设置的访问令牌,这会导致 API 服务器出现进一步的未经授权的错误,因为使用无效令牌发出请求。

如果我再次刷新浏览器,用户信息和订单都会使用上一个中间件流程中正确更新的令牌获取。

所以我不确定这里发生了什么,我想知道这是不是因为 req.session 对象没有及时被持久化回会话存储以等待下一个请求的时间问题?

有人知道这里可能发生了什么吗?

谢谢

更新 1

根据 cmets 中的要求,这里是正在发出的两个请求的请求和响应标头。

第一个请求(使用更新的令牌服务器端)

请求标头

GET /api/userinfo HTTP/1.1
Host: localhost:5000
Connection: keep-alive
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
Referer: https://localhost:5000/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Cookie: interact.sid=s%3A0NDG_bn67NeGQAYl1wP1-TmM19ExavFm.Zjv65e9BtSyNBuo%2FDxZEk2Np0963frVur4zHyYw3y5I

响应标头

HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=86400
X-Download-Options: noopen
X-XSS-Protection: 1; mode=block
Content-Type: text/html; charset=utf-8
Content-Length: 364
ETag: W/"16c-4AIbpZmTm3I+Yl+SbZdirw"
set-cookie: interact.sid=s%3A0NDG_bn67NeGQAYl1wP1-TmM19ExavFm.Zjv65e9BtSyNBuo%2FDxZEk2Np0963frVur4zHyYw3y5I; Path=/; Expires=Fri, 13 May 2016 11:54:56 GMT; HttpOnly; Secure
Date: Fri, 13 May 2016 11:24:56 GMT
Connection: keep-alive

第二次请求(使用旧令牌服务器端)

请求标头

GET /api/customers HTTP/1.1
Host: localhost:5000
Connection: keep-alive
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
Referer: https://localhost:5000/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Cookie: interact.sid=s%3A0NDG_bn67NeGQAYl1wP1-TmM19ExavFm.Zjv65e9BtSyNBuo%2FDxZEk2Np0963frVur4zHyYw3y5I

响应标头

HTTP/1.1 401 Unauthorized
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=86400
X-Download-Options: noopen
X-XSS-Protection: 1; mode=block
set-cookie: interact.sid=s%3A0NDG_bn67NeGQAYl1wP1-TmM19ExavFm.Zjv65e9BtSyNBuo%2FDxZEk2Np0963frVur4zHyYw3y5I; Path=/; Expires=Fri, 13 May 2016 11:54:56 GMT; HttpOnly; Secure
Date: Fri, 13 May 2016 11:24:56 GMT
Connection: keep-alive
Content-Length: 0

更新 2

我还应该提到我正在为我的会话存储使用connect-mongo,我尝试使用默认内存存储,但存在相同的行为。

【问题讨论】:

    标签: angularjs node.js express express-session


    【解决方案1】:

    这听起来像是客户端的竞争条件,如果您正在执行 2 个请求(检查身份验证 - 然后获取数据),那么第二个(获取数据)是否嵌套在第一个调用中成功?还是同时线性调用两者?

    我的想法是:

    客户端——发送用户信息请求(sessionid 1)——服务器处理

    客户端 - 获取订单信息请求(sessionid 1) - 服务器处理

    服务器 - 响应用户信息 - 403 - 客户端更新会话 ID

    服务器 - 响应订单信息 - 403

    你真正想要的是:

    客户端 - 发送用户信息请求(会话 1) - 服务器处理

    服务器 - 获取用户信息请求 (403) - 客户端更新会话 ID

    客户端 - 获取订单信息请求(会话 2) - 服务器处理

    服务器 - 响应订单信息 - 实际结果

    【讨论】:

    • 嗨,安德鲁,不,这两个电话不是同时从客户端发出的。我正在使用 angularjs 承诺,并且在用户信息请求承诺解决之前,不会发出第二个客户端调用请求。我添加了一些广泛的服务器端日志记录,并且可以看到来自客户端的传入请求一个接一个地得到处理,令牌刷新介于两者之间。我希望它像您建议的那样简单!谢谢
    • 那么我也可以建议这个帖子:stackoverflow.com/questions/13090177/… - 将滚动标志设置为 true 以强制会话在每个请求上更新(以确保其不被缓存)
    • 如果使用 jsfiddle 或 chrome 中的网络选项卡之类的东西,你能看到你对服务器的请求设置了哪些标头吗?你能把它们粘贴到你原来的问题中吗?
    • 嗨 Andrew,我已经在我的原始问题中提供了标题,请有空的时候看看。感谢您的宝贵时间!
    • 我唯一的另一个想法是 angular 在单个页面上的调用之间在内部缓存 cookie,因此您可能需要在调用 api 之间手动刷新它(这将通过浏览器发生默认情况下刷新我相信这就是为什么刷新时它可以)看看stackoverflow.com/questions/18809085/… - 看看你是否可以使用 cookieStore 在你的第一个身份验证请求失败时清除当前一个(在第二个发生之前)
    猜你喜欢
    • 2014-02-03
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    • 1970-01-01
    • 1970-01-01
    • 2021-02-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多