我有一些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
正如注释中所要求的那样,下面是正在发出的两个请求的请求和响应头。
第一个请求(它使用更新的令牌服务器端)
请求头
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作为会话存储,我尝试过使用默认内存存储,但也存在相同的行为。
发布于 2016-05-12 08:14:28
这听起来像是一个竞争条件客户端,如果您正在执行2个请求(检查auth -然后获取数据),第二个(获取数据)是否嵌套在第一个调用成功中?还是你是在线性地同时打电话?
我的想法是:
客户端-发送用户信息请求(sessionid 1) -服务器处理
客户端-获取订单信息请求(sessionid 1) -服务器处理
服务器-响应用户信息- 403 -客户端更新会话id
服务器-响应订单信息- 403
你真正想要的是:
客户端-发送用户信息请求(会话1) -服务器处理
服务器-获取用户信息请求(403) -客户端更新会话id
客户端-获取订单信息请求(会话2) -服务器处理
服务器-响应顺序信息-实际结果
https://stackoverflow.com/questions/37132634
复制相似问题