2016-05-10 28 views
5

我有一些Express中间件处理来自我的客户端应用程序的GET请求,以便向使用OAuth2令牌的单独API服务器发出后续请求,我也在使用express-session用于存储这些令牌。自定义req.session属性值的更新似乎不够快速持续

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

这里是我的特快代码都涉及部分:

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承诺)

在我快速的应用程序的用户信息请求,从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为我的会话存储,我已尝试使用默认的内存存储,但存在相同的行为。

回答

3

它听起来像一个竞争条件客户端,如果你正在执行2个请求(检查auth - 然后获取数据)是第二个(获取数据)嵌套到第一个调用成功?或者你是同时线性调用两者吗?

我的想法是:

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

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

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

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

真的是你想要什么我S:

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

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

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

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

+0

嗨安德鲁,不,两个通话不被从客户端在同一时间做。我正在使用angularjs承诺,并且第二个客户端端调用请求不会在用户信息请求承诺解决之前完成。我已经添加了一些广泛的服务器端日志记录,并且可以看到来自客户端的传入请求会一个接一个地进行处理,令牌之间刷新令牌。我希望这是一个简单的问题,就像你所说的那样!谢谢 – mindparse

+0

然后我也可以建议这个职位:http://stackoverflow.com/questions/13090177/updating-cookie-session-in-express-not-registering-with-browser - 设置滚动标志为true以强制会话更新每个请求(以确保其不缓存) –

+0

谢谢,但我已经'滚动:true'设置 – mindparse