angularjs - 自定义 req.session 属性值的更新似乎持续得不够快

标签 angularjs node.js express express-session

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

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

这是我的 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 服务器获取。

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

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

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

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

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

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

所以我不确定这里发生了什么,我想知道这是否是 req.session 对象没有及时返回 session 存储以供下一次请求的时间问题捡起来?

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

谢谢

更新 1

按照评论中的要求,这里是两个请求的请求和响应 header 。

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

请求 header

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

响应 header

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

第二个请求(使用旧 token 服务器端)

请求 header

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

响应 header

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 作为我的 session 存储,我尝试使用默认内存存储,但存在相同的行为。

最佳答案

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

我的想法是:

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

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

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

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

你真正想要的是:

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

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

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

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

关于angularjs - 自定义 req.session 属性值的更新似乎持续得不够快,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37132634/

相关文章:

javascript - 具有动态 ng-repeat 项目名称的可重用指令

javascript - 在 AngularJS 指令中测试 mouseover 和 mouseout 事件

Mysql 的每一行与其他表中的数据一起获取

deployment - 如何自动化 Node.js 部署?

Express JS 模块解析来自 Postman 的表单数据文本

javascript - Meteor 1.4.1.1 在添加 Angular 模板时抛出错误

node.js - 在 PowerShell 中运行 Ionic 命令时出现问题

node.js - 为什么我在 Web 服务器上收到代理错误,但在本地主机上却没有?

javascript - 用 express + webpack-dev-middleware/webpack-hot-middleware 替换 webpack-dev-server

javascript - Angular 数据资源上的子字符串过滤器