【发布时间】: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