SPA和SSO中无状态身份validation的性能(单点login)

如果我有一个SPA(单页应用程序 – 使用BackboneJS开发),并希望为其数据创build一个无状态的RESTful后端API。 我喜欢第三方单点login如何让用户如此轻松,因此会喜欢使用它。

但是我在这样的无状态环境中理解,每次请求都进行身份validation? 如果是这样,如果我正在使用第三方SSO,例如。 GitHub,我不需要每次都去GitHubvalidation用户身份吗? 这种情况最好的做法是什么? 我相信它是一个非常常见的用例? – 我允许用户通过Google / GitHub或其他东西login,然后从一些无状态的REST API获取数据

声明:)

为我的产品实现了这样的function,并分享了您的许多顾虑和技术(特别是使用100%无状态REST后端的骨干SPA),我可以告诉你我的这些事情,明确表示不想这样做成为“答案”,而是作为一个对话开始,从最后的讨论中学习,因为我觉得我对这个主题也有相当的了解。


首先,我认为你应该100%无国籍。 而100%,我的意思是100%:)不仅你的API层应该是无状态的,而是整个应用程序(当然除了客户端)。 在不同的层上移动会话(例如,redis)只是将问题稍微移动一点,但并不能解决问题。 一切(特别是缩放)将变得如此简单,而且稍后你会感谢你自己的这个决定。

所以,是的,你需要对每个请求进行身份validation 。 但是,这并不意味着您每次都必须访问auth提供程序。 我学到的一件事情是允许用户通过FB / GitHub /无论什么(从现在起, 远程服务 )进行authentication,只是缓解注册/login的痛苦的意思,没有别的。 你仍然需要增加用户的个人数据库。 当然,每个用户都会被关联到一个“远程”用户,但是在authentication执行后不久,应用程序应该引用“你的”用户,而不是“远程”用户(例如GitHub用户)。

履行

这是我已经实现的:

  1. 我的API方法总是需要一个身份validation令牌。 身份validation令牌是代表我系统用户的哈希,因此,当我调用POST /api/board?name=[a_name]&auth=[my_token] ,我知道是谁在调用,可以检查权限并可以关联新创build的实体board到正确的用户。

  2. 该令牌与远程服务令牌无关。 他们的计算逻辑是特定于我的应用程序。 但它映射了我的用户,也被映射到远程用户,所以在需要的情况下不会丢失任何信息。

  3. 以下是我通过远程服务validation用户的方式。 我执行服务文档中指定的远程authentication。 通常它是OAuth或类似OAuth的,这意味着最终我得到一个代表远程用户的authToken 。 这个令牌有两个目的:

    • 我可以用它来调用作为用户的远程服务的API方法
    • 我保证,用户至less是通过远程服务的方式expression的
  4. 只要用户使用远程服务进行身份validation,就可以在系统中加载或创build匹配的用户。 如果您的系统中没有remote_id: GitHub_abc123用户,则创build它,否则加载它。 假设这个用户的id: MyApp_def456 。 你也可以用自己的逻辑创build一个authToken ,代表用户MyApp_def456并把它传递给客户端(cookies是好的!!)

  5. 回到第1点:)


笔记

authentication是在每个请求执行的,这意味着你处理哈希和encryption函数,由定义是缓慢的。 现在,如果你用20次迭代使用bcrypt ,这会杀死你的应用程序。 我使用它来存储用户login时的密码,但是对于authToken (我个人使用的是一个与SHA-256相当的哈希)使用较不重要的algorithm。 这个令牌可以是短暂的(假设比平均攻击时间less),并且在服务器上相当容易计算。 这里没有确切的答案。 尝试不同的方法,测量和决定。 相反,我确实喜欢有这样的问题,而不是会话问题。 如果我需要计算更多散列,或者更快,我增加CPU功率。 使用会话和集群环境,您会遇到内存问题,负载平衡和粘滞会话问题或其他移动块(redis)。

事实上, HTTPS是绝对必须的,因为authToken总是作为parameter passing。

我将要实现的方式是在客户端(Backbone)和RESTful Web服务器之间引入一个代理。 该代理与SSO一起pipe理用户的身份validation。 因此,不需要更改API和/或客户端/networking服务器。 在这里快速演示:

 var http = require('http'), httpProxy = require('http-proxy'), express = require('express'); var proxy = new httpProxy.RoutingProxy(); var app = express(); function ensureAuthenticated(req, res, next) { if (isLoggedIn) { return next(); } res.redirect('/'); } // This should be your (RESTful) webserver http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write('request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2)); res.end(); }).listen(9000); var isLoggedIn = false; app.get('/', function(req, res){ console.log(isLoggedIn) res.send('Logged in? ' + isLoggedIn); }); app.get('/login', function(req, res){ isLoggedIn = true; res.redirect('/'); }); app.get('/logout', function(req, res){ isLoggedIn = false; res.redirect('/'); }); app.all('/api/*', ensureAuthenticated, function(req, res) { return proxy.proxyRequest(req, res, { host: 'localhost', port: 9000 }); }); app.listen(8000); 

当你第一次访问这个页面时,你已经注销,任何对/api/something调用被redirect到/ 。 当您login(访问/login页面)时,所有到/api/*请求将通过代理路由到监听端口9000的Web服务器。

特别是,当你设置app.all('/*', ...)所有对你的API服务器的调用保持不变,但是增加了一个authentication层。 这个概念对于oauth来说是微不足道的(如果你使用节点,可以看一看passportjs )。

你可以采用Facebook的JavaScript SDK中使用的方法。

此文档页面提供了通过FacebookloginWeb的快速入门。 不是很深,但解释了如何使用他们的方法的基本。 但是,没有提到签名的可能性。

当注册Facebooklogin您的应用程序时,您从Facebook上的应用程序仪表板获得一个应用程序的秘密。

当用户通过Facebooklogin到您的应用程序时,您的JavaScript将获得一个身份validation对象。 这个对象包含一个签名。 (如果您在仪表板中设置正确)

您可以将此客户端调用的身份validation对象提供给您的RESTful服务器,并在服务器上检查签名是否正确。 通过这种方式,您知道用户已通过Facebook进行身份validation,是该用户,并已为您的应用程序进行身份validation。

本文档页面介绍了如何使用签名authentication。 不要被标题中的“游戏”所吓倒,它对于任何networking应用程序来说都是完美的。

您可以使用其他OAUTH提供程序来实现与FBlogin相同的精神,而不是仅允许Facebook使用SSO。

使用由danielepolencicbuild议的解决scheme,但修改它,而不是在同一个node.js实例中的代理,您有另一台login服务器。 此服务与提供者进行OAUTH检查,并维护与客户端的会话。 它向客户发出一个签名的令牌,并且生存时间很短。 客户必须在生存时间结束之前要求新的令牌。

然后,实现一个客户端JavaScript,其function类似于Facebook JavaScript SDK,供您的应用程序使用该login。 该function可以轮询新的令牌,也可以根据请求检索新的令牌,无论该场景是否最有效。

客户端将此令牌提供给每个请求上的RESTful API,并由服务器检查签名。 就像Facebook的SSO一样。

还有一个会话,但它可以维护visavis完全不同的机器。 这个服务可以通过RESTful API独立于服务器进行扩展。

但是,请记住,这种方法可能容易受到中间人攻击和重放攻击。 在不使用https的情况下使用可能不明智。