【问题标题】:RESTful web service - how to authenticate requests from other services?RESTful Web 服务 - 如何验证来自其他服务的请求?
【发布时间】:2011-09-02 07:23:46
【问题描述】:

我正在设计一个需要用户访问的 RESTful Web 服务,以及其他 Web 服务和应用程序。所有传入的请求都需要进行身份验证。所有通信都通过 HTTPS 进行。用户身份验证将基于身份验证令牌工作,该令牌通过将用户名和密码(通过 SSL 连接)发布到服务提供的 /session 资源来获取。

在 Web 服务客户端的情况下,客户端服务背后没有最终用户请求由计划任务、事件或其他一些计算机操作发起。连接服务列表是事先知道的(显然,我猜)。 我应该如何对来自其他(网络)服务的这些请求进行身份验证?我希望为这些服务实施身份验证过程尽可能简单,但不以牺牲安全为代价。对于这样的场景,标准和最佳做法是什么?

我能想到(或已向我建议)的选项:

  1. 让客户端服务使用“假”用户名和密码,并以与用户相同的方式对其进行身份验证。我不喜欢这个选项 - 它只是感觉不对。

  2. 为客户端服务分配一个永久的应用程序 ID,也可能是一个应用程序密钥。据我了解,这与拥有用户名+密码相同。使用这个 id 和密钥,我可以对每个请求进行身份验证,或者创建一个身份验证令牌来对进一步的请求进行身份验证。无论哪种方式,我都不喜欢这个选项,因为任何可以获取应用程序 ID 和密钥的人都可以冒充客户端。

  3. 我可以在之前的选项中添加 IP 地址检查。这将使执行虚假请求变得更加困难。

  4. 客户端证书。设置我自己的证书颁发机构,创建根证书,并为客户端服务创建客户端证书。不过,我想到了几个问题:a) 我如何仍然允许用户在没有证书的情况下进行身份验证,以及 b) 从客户端服务的角度来看,这种场景的实现有多复杂?

  5. 其他的 - 肯定有其他解决方案吗?

我的服务将在 Java 上运行,但我故意遗漏了有关它将建立在什么特定框架上的信息,因为我对基本原理更感兴趣,而不是对实现细节感兴趣 - 我假设最好的解决方案因为无论底层框架如何,这都可以实现。但是,我对这个主题有点缺乏经验,所以关于实际实现的具体提示和示例(如有用的第三方库、文章等)也将不胜感激。

【问题讨论】:

  • 如果我可以建议,请熟悉大型网站服务并选择您喜欢的内容。您的用户还会发现与其他 RESTful 服务的最佳实践有相似之处。
  • 发现另一个涉及类似主题的问题(大约两年前):stackoverflow.com/questions/1138831/…
  • 服务(网络和其他)托管在什么操作系统上?它们是否在属于同一基础架构的服务器上运行?
  • 操作系统可能会有所不同:Win、*nix 等。客户端服务可能与我的服务在同一基础架构内,也可能不在。

标签: web-services authentication rest restful-authentication client-certificates


【解决方案1】:

5.其他的 - 肯定有其他解决方案吗?

你是对的,有!它被称为JWT(JSON Web Tokens)。

JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全传输信息。此信息可以验证和信任,因为它是数字签名的。 JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 的公钥/私钥对进行签名。

我强烈建议您研究 JWT。与其他解决方案相比,它们是解决问题的更简单的方法。

https://jwt.io/introduction/

【讨论】:

    【解决方案2】:

    此问题的任何解决方案都归结为共享秘密。我也不喜欢硬编码的用户名和密码选项,但它确实具有非常简单的好处。客户证书也不错,但真的有很大不同吗?服务器上有一个证书,客户端上有一个证书。它的主要优点是更难暴力破解。不过,希望您有其他保护措施来防止这种情况发生。

    我认为您对客户端证书解决方案的 A 点并不难解决。您只需使用一个分支。 if (client side certificat) { check it } else { http basic auth } 我不是 java 专家,我从来没有使用它来做客户端证书。然而,快速谷歌将我们带到this tutorial,它看起来就在你的小巷里。

    尽管有所有这些“什么是最好的”讨论,但我要指出,还有另一种哲学说:“代码越少,聪明越少越好。” (我个人持有这种哲学)。客户端证书解决方案听起来像是很多代码。

    我知道您表达了有关 OAuth 的问题,但 OAuth2 提案确实包含一个名为“bearer tokens”的问题解决方案,它必须与 SSL 结合使用。我认为,为了简单起见,我会选择硬编码的用户/通行证(每个应用一个,以便可以单独撤销)或非常相似的不记名令牌。

    【讨论】:

    • 客户端证书不是共享机密。这就是它们存在的原因。客户端有一个私钥,服务器有一个公钥。客户端从不分享它的秘密,公钥也不是秘密。
    • 教程链接不会指向教程文章,而是指向 Oracle 网站上的一些 Java 索引页面...
    • @MarjanVenema 好吧,那是因为您在 newz2000 回答 +2 年后尝试链接,但您始终可以尝试 WayBack Machine:web.archive.org/web/20110826004236/http://java.sun.com/…
    • @MarjanVenema:很抱歉,但您希望 newz2000 在链接失效后来这里更新链接?就像你说的,它是链接腐烂,所以它迟早会发生。您要么尝试访问档案以查看作者当时看到的内容,要么找到新链接并做出积极贡献。我看不出你的评论对任何人有什么帮助。但在这里,请点击此链接:oracle.com/technetwork/articles/javase/…(注意它最终也会腐烂)
    • @FábioSilva:不。我不期望他这样做。我确实阅读了存档,但我没有时间去寻找新链接,所以我做了下一件最好的事情:发表评论说它已经死了,这样社区中的其他人就可以找到新位置并更新邮政。既然您显然有时间找到新链接,那么您为什么不更新帖子中的链接,而不是将其放在让我发牢骚的评论中?
    【解决方案3】:

    您可以在服务器上创建会话,并在每次 REST 调用时在客户端和服务器之间共享 sessionId

    1. 首先验证 REST 请求:/authenticate。使用sessionId: ABCDXXXXXXXXXXXXXX 返回响应(根据您的客户端格式);

    2. 将此sessionId 与实际会话一起存储在Map 中。 Map.put(sessionid, session) 或使用SessionListener 为您创建和销毁密钥;

      public void sessionCreated(HttpSessionEvent arg0) {
        // add session to a static Map 
      }
      
      public void sessionDestroyed(HttpSessionEvent arg0) {
        // Remove session from static map
      }
      
    3. 通过每次 REST 调用获取 sessionid,例如 URL?jsessionid=ABCDXXXXXXXXXXXXXX(或其他方式);

    4. 使用sessionId从地图中检索HttpSession
    5. 如果会话处于活动状态,则验证对该会话的请求;
    6. 发回响应或错误消息。

    【讨论】:

      【解决方案4】:

      阅读您的问题后,我想说,生成特殊令牌来执行所需的请求。此令牌将在特定时间内存在(假设是一天)。

      以下是生成身份验证令牌的示例:

      (day * 10) + (month * 100) + (year (last 2 digits) * 1000)
      

      例如: 2011 年 6 月 3 日

      (3 * 10) + (6 * 100) + (11 * 1000) = 
      30 + 600 + 11000 = 11630
      

      然后与用户密码连接,例如“my4wesomeP4ssword!”

      11630my4wesomeP4ssword!
      

      然后对该字符串进行MD5:

      05a9d022d621b64096160683f3afe804
      

      什么时候调用请求,总是使用这个令牌,

      https://mywebservice.com/?token=05a9d022d621b64096160683f3afe804&op=getdata
      

      这个令牌每天都是独一无二的,所以我想这种保护足以始终保护你的服务。

      希望有帮助

      :)

      【讨论】:

      • 我真的很喜欢你在每个请求中附加安全令牌的方式,但是当程序员已经创建了 100 个 jsp 页面并且之后让 t0 在之前创建的 100 个页面中实现安全性以及将要创建的页面。在这种情况下,在每个请求中附加令牌不是一个正确的选择。无论如何 +1 为您的技术。 :)
      • 时钟不同步会怎样?在这种情况下,客户端不会生成错误的令牌吗?即使两者都以 UTC 格式生成日期时间,它们的时钟仍然可能不同,从而导致每天都有时间窗口,此时令牌不起作用?
      • @NickG,我以前遇到过这个问题,这是通过请求服务器时间来保护这个问题的唯一方法。这将 99% 解决 UTC 的问题。当然,缺点是对服务器的额外调用。
      • 但我仍然可以使用此令牌使用您的网络服务一天,对吧?我不明白这会有什么帮助
      • @MinaGabriel 您可以在代币生成中添加更多时间范围。 (分 * 10) + (小时 * 100) + (日 * 1000) + (月 * 10000) + (年 (后两位数) * 100000)
      【解决方案5】:

      就客户端证书方法而言,实施起来并不难,同时仍然允许没有客户端证书的用户进入。

      如果您确实创建了自己的自签名证书颁发机构,并向每个客户端服务颁发客户端证书,那么您将有一种简单的方法来验证这些服务。

      根据您使用的 Web 服务器,应该有一种方法来指定客户端身份验证,该方法将接受客户端证书,但不需要。例如,在 Tomcat 中指定 https 连接器时,您可以设置“clientAuth=want”,而不是“true”或“false”。然后,您将确保将您的自签名 CA 证书添加到您的信任库(默认情况下,您正在使用的 JRE 中的 cacerts 文件,除非您在 Web 服务器配置中指定了另一个文件),因此唯一受信任的证书将是那些从您的自签名 CA。

      在服务器端,如果您能够从请求中检索客户端证书(非空),您将只允许访问您希望保护的服务,并且如果您更喜欢任何额外的安全性,则通过任何 DN 检查。对于没有客户端证书的用户,他们仍然可以访问您的服务,但请求中没有证书。

      在我看来,这是最“安全”的方式,但它肯定有其学习曲线和开销,因此不一定是满足您需求的最佳解决方案。

      【讨论】:

        【解决方案6】:

        我相信方法:

        1. 第一个请求,客户端发送 id/passcode
        2. 用 id/pass 换取唯一令牌
        3. 在每个后续请求中验证令牌,直到它过期

        无论您如何实施和其他具体技术细节,都是相当标准的。

        如果您真的想挑战极限,也许您可​​以将客户端的 https 密钥视为暂时无效状态,直到验证凭据,如果它们从未验证过,则限制信息,并在验证后再次根据到期时间授予访问权限.

        希望对你有帮助

        【讨论】:

          【解决方案7】:

          您可以采取几种不同的方法。

          1. RESTful 纯粹主义者希望您使用 BASIC 身份验证,并在每个请求上发送凭据。他们的理由是没有人存储任何状态。

          2. 客户端服务可以存储一个 cookie,它维护一个会话 ID。我个人并不觉得这像我听到的一些纯粹主义者那样令人反感——一遍又一遍地进行身份验证可能会很昂贵。不过,听起来你不太喜欢这个主意。

          3. 根据您的描述,听起来您可能对OAuth2 感兴趣。到目前为止,就我所见,我的经验是它有点令人困惑,而且有点前沿。那里有实现,但它们很少而且相差甚远。在 Java 中,我了解它已集成到 Spring3 的 security 模块中。 (他们的tutorial 写得很好。)我一直在等Restlet 是否会有扩展,但到目前为止,虽然已经提出,并且可能在孵化器中,但它仍然没有完全合并.

          【讨论】:

          • 我没有反对选项 2 - 我认为这是 RESTful 应用程序中的好解决方案 - 但是客户端服务首先从哪里获取令牌?他们如何第一次进行身份验证?也许我想错了,但客户端服务需要有自己的用户名和密码似乎很奇怪。
          • 如果最终用户是您的用户,那么中间服务可以在第一次请求时将其凭据传递给您,您可以返回 cookie 或其他令牌。
          • 类似地,在 OAuth 场景中,最终用户将他们对 Web 服务的访问权限委托给中间服务。
          • 似乎有一个误解——客户端服务背后根本没有最终用户。我更新了我的问题以更好地解释情况。
          • 我只想补充一点,上面列出的 #1 选项只能通过 HTTPS 完成。
          【解决方案8】:

          除了身份验证之外,我建议您考虑全局。考虑让您的后端 RESTful 服务无需任何身份验证;然后在最终用户和后端服务之间放置一些非常简单的身份验证所需的中间层服务。

          【讨论】:

          • 在最终用户和后端服务之间?这不会让客户服务完全未经身份验证吗?这不是我想要的。我当然可以在我的 Web 服务和客户端服务之间放置中间层,但这仍然存在一个问题:实际的身份验证模型是什么?
          • 中间层可以是Nginx等web服务器,可以在那里做认证。身份验证模型可以是基于会话的。
          • 正如我试图在我的问题中解释的那样,我不希望这些客户端服务使用基于会话的身份验证方案。 (请参阅我对问题的更新。)
          • 我建议您使用白 ip 列表或 ip 范围代替名称和密码。通常,客户端服务ip是稳定的。
          【解决方案9】:

          我会让应用程序使用应用程序 id 参数将用户重定向到您的站点,一旦用户批准请求,生成一个唯一令牌,其他应用程序使用该令牌进行身份验证。这样,其他应用程序不会处理用户凭据,并且其他应用程序可以由用户添加、删除和管理。 Foursquare 和其他一些网站以这种方式进行身份验证,并且很容易作为其他应用程序实现。

          【讨论】:

          • 嗯,我不确定我是否能够按照解释进行操作。我们在谈论哪个用户?我说的是应用程序与另一个应用程序通信。我想你明白这一点,但我还是不太明白。例如,当这个“令牌”过期时会发生什么?
          • 嗯,您生成并发送回其他应用程序的令牌是一个持久性令牌,它与用户和应用程序相关联。这是foursquares 文档developer.foursquare.com/docs/oauth.html 的链接,它基本上是oauth2,因此请查看它以获得良好的身份验证解决方案。
          • 似乎有一个误解——客户端服务背后根本没有最终用户。不过,您链接到的foursquare 文档确实简要提到了无用户访问,所以它至少有点帮助-谢谢!但我仍然无法完整了解它在现实中的工作方式。
          • 只需为应用程序生成密钥,如果您所做的只是允许应用程序访问,那么简单的 application_id 和 application_key 应该可以用于身份验证。如果您想让他们使用令牌进行身份验证,请查看使用设计的令牌身份验证选项,因为它只是通过 url 请求传递给您的应用程序的参数。
          • 但这不是和用户名+密码=会话认证令牌场景完全一样吗? application_id 和 application_key 不只是用户名和密码的同义词吗? :) 如果这真的是这种情况的标准做法,那很好 - 正如我所说,我对此缺乏经验 - 但我只是认为可能还有其他选择......
          猜你喜欢
          • 1970-01-01
          • 2015-07-28
          • 1970-01-01
          • 2011-01-18
          • 1970-01-01
          • 2021-12-10
          • 2017-04-02
          • 1970-01-01
          • 2023-03-24
          相关资源
          最近更新 更多