6

如果我有一个 SPA(单页应用程序 - 使用 BackboneJS 开发)并希望为其数据提供一个无状态的 RESTful 后端 API。我喜欢 3rd 方单点登录如何让用户的事情变得如此简单,因此会喜欢它使用它。

但是我知道在这样的无状态环境中,每个请求都会进行身份验证?如果是这样,如果我使用的是第 3 方 SSO,例如。GitHub,我不是每次都需要去 GitHub 对用户进行身份验证吗?这种情况的最佳做法是什么?我相信这是一个非常常见的用例?- 我允许用户通过 Google/GitHub 或其他方式登录,然后从一些无状态的 REST API 获取数据

4

3 回答 3

11

免责声明:)

为我的产品实现了这样的事情,并分享了您的许多关注点和技术(尤其是使用 100% 无状态 REST 后端的 Backbone 的 SPA)我可以告诉您我对此的想法,并明确表示这不想成为“答案”,而是从讨论结果中学习的对话发起者,因为我认为我在这个话题上也有很多东西要学习。


首先,我认为你应该 100% 无国籍。100%,我的意思是 100% :) 不仅您的 API 层应该是无状态的,而且整个应用程序(当然客户端除外)都应该是无状态的。在不同的层(例如redis)上移动会话只是稍微移动了问题,但并没有解决它。一切(尤其是缩放)都会变得容易得多,稍后你会感谢自己做出这个决定。

所以,是的,您需要对每个请求进行身份验证。但这并不意味着您每次都必须访问身份验证提供程序。我学到的一件事是,允许用户通过 FB/GitHub/Whatever(从现在开始,远程服务)进行身份验证只是缓解注册/登录痛苦的一种手段,仅此而已。您仍然需要扩展您的个人用户数据库。当然,每个用户都将关联到一个“远程”用户,但在执行身份验证后不久,应用程序应该引用“您的”用户,而不是“远程”用户(例如 GitHub 用户)。

执行

这是我已经实现的:

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

  2. 所述令牌与远程服务的令牌无关。计算它们的逻辑特定于我的应用程序。但它映射了我的一个用户,该用户也映射到了一个远程用户,因此在需要时不会丢失任何信息。

  3. 这是我通过远程服务对用户进行身份验证的方式。我实现了服务文档中指定的远程身份验证。通常它是 OAuth 或 OAuth-like,这意味着最后我得到一个authToken代表远程用户的。此令牌有 2 个用途:

    • 我可以使用它来作为用户调用远程服务上的 API 方法
    • 我保证用户就是它所说的那个人,至少通过远程服务的方式
  4. 一旦您的用户通过远程服务验证了自己,您就可以在系统中加载或创建匹配的用户。如果remote_id: GitHub_abc123您的系统中不存在该用户,则创建它,否则加载它。假设这个用户有id: MyApp_def456. 您还可以authToken使用自己的逻辑创建一个代表用户MyApp_def456并将其传递给客户端(cookie 可以!!)

  5. 回到第 1 点 :)


笔记

每次请求都会执行身份验证,这意味着您要处理散列和加密函数,根据定义,这些函数很慢。现在,如果您使用bcrypt20 次迭代,这将杀死您的应用程序。我使用它来存储用户登录时的密码,但随后使用较轻的算法authToken(我个人使用的哈希值可与SHA-256)。这些令牌可以是短暂的(比方说比破解它们的平均时间要短)并且在服务器机器上相当容易计算。这里没有确切的答案。尝试不同的方法,衡量和决定。相反,我可以肯定的是,我更喜欢遇到此类问题而不是会话问题。如果我需要计算更多或更快的哈希值,我会增加 CPU 能力。对于会话和集群环境,您会遇到内存问题、负载平衡和粘滞会话问题,或其他移动部件 (redis)。

Obiouvsly,HTTPS是绝对强制性的,因为authToken总是作为参数传递。

于 2013-08-20T15:18:03.003 回答
1

我实现它的方式是在客户端(Backbone)和 RESTful 网络服务器之间引入一个代理。代理与 SSO 一起管理用户的身份验证。因此无需更改 api 和/或客户端/网络服务器。这是一个快速演示:

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都会重定向到/. 当您登录(访问/login页面)时,所有请求/api/*都通过代理路由到侦听端口 9000 的网络服务器。

特别是,当您将app.all('/*', ...)所有对 API 服务器的调用设置为保持不变但增加了身份验证层时。使用 oauth 扩展这个概念很简单(如果您使用的是 node,请查看passportjs )。

于 2013-08-16T10:43:01.800 回答
0

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

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

为您的应用程序注册 Facebook 登录时,您会从 Facebook 上的应用程序仪表板获得一个应用程序密码。

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

您可以通过客户端调用向您的 RESTful 服务器提供此身份验证对象,并在服务器上检查签名是否正确。这样您就知道该用户已通过 facebook 身份验证,是该用户,并且已针对您的应用程序进行身份验证。

本文档页面描述了如何使用签名身份验证。不要被标题中的“游戏”吓倒,它适用于任何网络应用程序。

您可以使用其他 OAUTH 提供程序,以与 FB 登录相同的精神实现某些东西,而不是只允许 Facebook 进行 SSO。

使用 danielepolencic 建议的解决方案,但对其进行修改,以便在同一 node.js 实例中使用另一台服务器而不是代理来登录。该服务与提供者进行 OAUTH 检查,并与客户端保持会话。它向客户端发出一个签名的令牌,具有很短的生存时间。客户端必须在生存时间结束之前请求一个新的令牌。

然后,您实现一个客户端 JavaScript,其功能类似于 Facebook JavaScript SDK,以供您的应用程序使用登录。这个函数既可以轮询新的令牌,也可以根据请求检索一个新的令牌,这对场景来说是最有效的。

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

还是有一个session,但是visvis完全可以在不同的机器上维护。该服务可以使用您的 RESTful api 独立于服务器进行扩展。

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

于 2013-08-19T17:30:14.633 回答