RESTful Web服务-如何验证来自其他服务的请求?


117

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

对于Web服务客户端,客户端服务后面没有最终用户这些请求是由计划任务,事件或某些其他计算机操作启动的。连接服务的列表是事先已知的(显然,我想)。我应该如何验证来自其他(网络)服务的这些请求?我希望对这些服务的身份验证过程尽可能地容易实现,但是不以安全为代价。对于这种情况,标准和最佳做法是什么?

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

  1. 让客户端服务诉诸于“假”用户名和密码,并以与用户相同的方式对它们进行身份验证。我不喜欢此选项-感觉不正确。

  2. 为客户端服务分配一个永久的应用程序ID,也可以分配一个应用程序密钥。据我了解,这与使用用户名+密码相同。使用此ID和密钥,我可以对每个请求进行身份验证,也可以创建身份验证令牌来对其他请求进行身份验证。无论哪种方式,我都不喜欢此选项,因为任何可以保留应用程序ID和密钥的人都可以模拟客户端。

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

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

  5. 还有什么-那里必须有其他解决方案吗?

我的服务将在Java上运行,但是我故意遗漏了将在其上构建哪种特定框架的信息,因为我对基本原理更感兴趣,而对实现细节却不太感兴趣-我认为这是最好的解决方案无论底层框架如何,都可以实现。但是,我对这个主题没有多少经验,因此,有关实际实现的具体技巧和示例(例如有用的第三方库,文章等)也将不胜感激。


如果我建议,请熟悉大型网站服务,然后选择所需的内容。您的用户还会发现与其他RESTful服务的最佳实践有相似之处。
伊兹密尔·拉米雷斯

找到了另一个涉及相似主题的问题(将近两年):stackoverflow.com/questions/1138831/…–
Tommi,

服务(网络和其他)都托管在什么操作系统上?它们是否在属于相同基础结构的服务器上运行?
安德斯·亚伯

操作系统可能有所不同:Win,* nix等。客户端服务可能与我的服务位于同一基础架构中,也可能不在同一基础架构中。
汤米(Tommi)

Answers:


34

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

我认为您很难解决客户端证书解决方案的问题A。您只使用一个分支。if (client side certificat) { check it } else { http basic auth }我不是Java专家,而且我从未与它合作来做客户端证书。但是,快速的Google会带我们进入本教程,它看起来正好位于您的小巷。

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

我知道您表达了有关OAuth的问题,但是OAuth2提案确实包含了针对您的问题的一种解决方案,即“ 承载令牌 ”,必须与SSL结合使用。我认为,为了简单起见,我会选择硬编码的用户名/密码(每个应用一个),或者选择非常相似的承载令牌。


27
客户端证书不是共享机密。这就是为什么它们存在。客户端具有私钥,服务器具有公钥。客户端永远不会分享其秘密,而公钥也不是秘密。
蒂姆(Tim)

5
教程链接不会导致产生教程文章,而是会导致Oracle网站上的某些Java索引页面……
Marjan Venema

2
@MarjanVenema嗯,那是因为你想链接2年后newz2000回答,但你总是可以尝试Wayback机器:web.archive.org/web/20110826004236/http://java.sun.com/...
法比奥Duque Silva 2014年

1
@MarjanVenema:对不起,但是您希望newz2000失效后会在这里更新链接吗?如您所说,它是链接腐烂,所以迟早会发生。您可以尝试访问归档文件以查看作者当时所看到的内容,或者找到新链接并做出积极贡献。我看不到您的评论对任何人都有帮助。但在这里,请点击此链接:oracle.com/technetwork/articles/javase/...(注意,它最终会腐烂太)
法比奥·席尔瓦杜克

2
@FábioSilva:不,我不希望他这样做。我确实阅读了档案,但是我没有时间去寻找新的链接,所以我做了下一件最好的事情:发表评论,说它已经死了,以便社区中的其他人可以找到新位置并更新该位置。发布。您显然有时间找到新链接,为什么不更新帖子中的链接,而不是在困扰我的位置添加评论?
Marjan Venema 2014年

36

阅读完您的问题后,我会说,生成特殊令牌以执行所需的请求。该令牌将在特定的时间(比如说一天)存在。

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

(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

该令牌每天都是唯一的,因此我想这种保护足以永远保护您的服务。

希望有所帮助

:)


1
我真的很喜欢您在每个请求中附加安全令牌的方式,但是当程序员已经创建了100个jsp页面并且之后t0在先前创建的100个页面以及将要创建的页面中实现了安全性时,会发生什么情况。在这种情况下,在每个请求中添加令牌的方法都不正确。无论如何,您的技术都需要+1。:)
Ankur Verma

4
如果时钟不同步会怎样?客户端在这种情况下不会生成错误的令牌吗?即使两者都使用UTC生成日期时间,它们的时钟也可能会有所不同,导致每天出现一个时间窗口,此时令牌不起作用?
NickG

@NickG,我以前遇到过此问题,这是通过请求服务器时间来保护此问题的唯一方法。这将99%消除UTC问题。当然,不利的一面是对服务器的额外呼叫。
2012年

但我仍然可以使用此令牌使用您的Web服务一天吗?我不知道这有什么帮助
Mina Gabriel

@MinaGabriel您可以在代币生成中添加更多时间范围。(分钟* 10)+(小时* 100)+(天* 1000)+(月* 10000)+(年(最后2位数字)* 100000)
kororo

11

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

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

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

  3. 从您的描述来看,这听起来确实像您对OAuth2感兴趣。到目前为止,从我所看到的经验来看,这是一种令人困惑的过程,并且是一种前沿。那里有许多实现,但它们之间相差无几。在Java中,我知道它已经集成到Spring3的安全模块中。(他们的教程写得很好。)我一直在等待,看看Restlet中是否会有扩展,但是到目前为止,尽管它已经被提议,并且可能在孵化器中,但是它仍然没有被完全合并。


我对选项2没有什么反对-我认为这在RESTful应用中是很好的解决方案-但是客户端服务首先从哪里获得令牌?他们如何第一次进行身份验证?也许我在想这是错误的,但是客户端服务为此需要具有自己的用户名和密码似乎很奇怪。
汤米(Tommi)

如果最终用户是您的用户,则中间服务可以在第一个请求时将其凭据传递给您,并且您可以返回cookie或其他令牌。
2011年

同样,在OAuth方案中,最终用户将中介服务委派给他们对您的Web服务的访问权限。
jwismar 2011年

似乎有一个误解- 客户端服务背后根本没有最终用户。我已经更新了我的问题,以更好地解释这种情况。
Tommi

1
我只想补充一点,上面列出的#1选项只能通过HTTPS完成。
mr-sk

3

我相信这种做法:

  1. 第一次请求,客户端发送ID /密码
  2. 将ID /通行证交换为唯一令牌
  3. 在每个后续请求上验证令牌,直到令牌过期

不管您如何实施以及其他特定的技术细节,它都是非常标准的。

如果您确实想推动信封使用,也许您可​​以将客户端的https密钥视为暂时无效的状态,直到验证凭据为止;如果凭据从未验证过,则限制其信息;而在验证凭据时,再次基于到期时授予访问权限。

希望这可以帮助


3

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

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

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

在服务器端,仅当您能够从请求中检索客户端证书(不为null)时,才允许访问您想要保护的服务,并在需要任何额外安全性时通过任何DN检查。对于没有客户端证书的用户,他们仍然可以访问您的服务,但在请求中将不包含任何证书。

我认为这是最“安全”的方式,但是它当然具有学习曲线和开销,因此不一定是满足您需求的最佳解决方案。


3

5.还有其他东西-那里必须有其他解决方案吗?

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

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

我强烈建议您研究JWT。与替代解决方案相比,它们是解决问题的简单得多的解决方案。

https://jwt.io/introduction/


1

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

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

  2. 这存放sessionIdMap实际会话。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. 使用检索HttpSession地图sessionId;
  5. 如果会话处于活动状态,则验证对该会话的请求;
  6. 发回响应或错误消息。

0

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


嗯,我不确定我是否能够按照说明进行操作。我们在说哪个用户?我说的是应用程序与另一个应用程序通信。我想你明白了,但我还是不太明白。例如,当该“令牌”过期时会发生什么?
汤米(Tommi)

那么,您生成并发送回另一个应用程序的令牌是一个持久令牌,它与用户和应用程序绑定在一起。这里是到foursquares docs developer.foursquare.com/docs/oauth.html的链接,它基本上只是oauth2,所以请研究一下它以提供一个好的身份验证解决方案。
Devin M

似乎有一个误解- 客户端服务背后根本没有最终用户。不过,您链接到的foursquare文档确实简要提到了无用户访问,因此至少有一定帮助-谢谢!但是我仍然无法完全了解它在现实中的工作方式。
Tommi

只需为应用程序生成密钥,然后,如果您要做的就是允许访问应用程序,则简单的application_id和application_key应该可以用于身份验证。如果您想让它们使用令牌进行身份验证,请考虑使用devise的令牌身份验证选项,因为它只是随url请求一起传递给您的应用程序的参数。
Devin M

难道这不是和用户名+密码=会话身份验证令牌方案完全相同吗?application_id和application_key不仅仅是用户名和密码的同义词吗?:)如果这真的是这种情况的标准做法,那完全没问题-正如我所说,我对此没有经验-但我只是认为可能还有其他选择...
Tommi,

-3

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


最终用户和后端服务之间?这样是否会使客户端服务完全未经身份验证?这不是我想要的。我当然可以在Web服务和客户端服务之间放置中间层,但这仍然使问题悬而未决:实际的身份验证模型是什么?
Tommi

中间层可以是Web服务器,例如Nginx,您可以在那里进行身份验证。认证模型可以基于会话。
大港

正如我试图在我的问题中解释的那样,我不希望这些客户端服务使用基于会话的身份验证方案。(请参阅我对问题的更新。)
汤米(Tommi

我建议您使用白色IP列表或IP范围,而不要使用名称和密码。通常,客户端服务ip是稳定的。
大港
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.