Web应用和移动应用的REST API身份验证

63
我在决定如何为一个RESTful API实现认证,以便于网络应用和移动应用都能够安全地使用时遇到了一些问题。
首先,我考虑使用HTTP基本身份验证加上HTTPS。对于移动应用程序来说,这将非常有效,因为用户名和密码可以安全地存储在OS密钥链中,并且由于请求是通过HTTPS完成的,所以不能被拦截。对于API来说,它完全是无状态的,也很优雅。但是对于网络应用程序来说,没有访问这样的密钥链来存储用户名和密码,所以我需要使用cookie或localStorage,但这将把用户的私人信息存储在一个容易访问的地方。
经过更多的研究,我发现了很多关于HMAC认证的讨论。我看到的问题是需要有一个共享的秘密,只有客户端和服务器知道。我如何将此用户特定的秘密传递给网络应用程序中的特定用户,除非我有一个接受用户名/密码并返回密钥以存储在cookie中的api/login端点,以供未来请求使用。但是这会向API引入状态。
为了将局面变得更加复杂,我想能够限制API仅允许特定的应用程序(或者能够阻止某些应用程序使用API)。我无法看到如何在网络应用程序完全公开的情况下实现这一点。
我真的不想使用OAuth。这可能超出了我的需求范围。
我感觉自己可能没有完全理解HMAC,因此我欢迎一个解释,并了解如何使用它来为网络应用和移动应用提供安全性。
更新:
最终我使用了HTTP基本身份验证,但是不是每次请求提供实际的用户名和密码,而是实现了一个端点以交换用户名和密码获取访问密钥,然后为每个经过身份验证的请求提供该密钥。这消除了在浏览器中存储用户名和密码的问题,当然,如果有人能够访问机器并使用它,仍然可以窃取令牌。回顾一下,我可能会进一步研究OAuth,但对于初学者来说,它非常复杂。

4
我不是很想实现OAuth,对我的需求来说可能过于复杂了……不,它正是你所需要的 :-) - Jørn Wildt
你是如何解决你的身份验证问题的? - akohout
3个回答

32
你应该使用OAuth2。下面是具体步骤:
1)移动应用程序
移动应用程序将客户端凭据存储在本地。接着,它使用“资源所有者密码凭证授权”(参见https://www.rfc-editor.org/rfc/rfc6749#section-4.3)发送这些凭据。然后,它会获得一个(承载)令牌,可以在以下请求中使用。
2)网站
网站使用“授权码授权”(参见https://www.rfc-editor.org/rfc/rfc6749#section-4.1):
1. 网站检测到未经授权的请求并将浏览器重定向到REST API中启用HTML的授权终点。 2. 用户通过REST服务进行身份验证。 3. REST网站将用户重定向回网站,并在URL中附带访问令牌。 4. 网站调用REST网站并交换访问令牌以获取授权令牌。
这里之后,网站使用授权令牌来访问REST服务(代表最终用户),通常是通过在HTTP授权标头中将令牌作为“bearer”令牌包含在内。这并不是什么高深技术,但需要一些时间来完全理解。
3)限制某些应用程序对API的访问
在OAuth2中,每个客户端都会被分配一个客户端ID和客户端密钥(这里的“客户端”是您的移动应用程序或网站)。客户端在授权时必须发送这些凭据。您的REST服务可以使用此来验证调用客户端。

5
Web 应用的客户端密钥如何保密?源代码对所有人都是公开的,是什么阻止有人窃取它并用于未授权的应用程序? - maknz
1
使用普通的服务器配置,不要在任何地方硬编码客户端密钥。让网站从一个不属于源代码仓库的文件中读取密钥。这很容易实现。实际上你应该担心的是移动客户端,因为已经下载的人可以从中提取客户端密钥。我还没有看到解决这个问题的方法。 - Jørn Wildt
@JørnWildt 为什么不在网站上也使用“资源所有者密码授权”呢?实际上,在用户登录后返回刷新令牌和访问令牌可以避免为进一步的请求存储用户名和密码,例如在这里解释的那样:http://techblog.hybris.com/2012/06/11/oauth2-resource-owner-password-flow/ - Mik378
1
@Mik378 因为ROPCG强制最终用户向一个不应该需要它们的网站发送其凭据(以便让该网站在REST API上使用其凭据)。使用ACG,用户只需将其凭据发送到实际“拥有”凭据(REST API)的站点。 - Jørn Wildt
没有针对移动应用程序的解决方案,其client_secret必须被视为公共信息(因为反编译、网络嗅探等原因):这些应用程序(移动应用程序和JavaScript应用程序)被称为公共客户端(RFC6749)。但是,您可以依靠DNS来确保只有您的应用程序将收到凭据。在Android和iOS上,可以分别使用App Link和Universal Links,如RFC8252所述。然而,从iOS11开始,您会遇到问题,因为新的SFAuthenticationSession不应与Univrsal Links一起使用(如iOS文档中所述)。 - Tangui
显示剩余2条评论

4
我为自己的API解决了这个问题,而且非常容易和安全,无需暴露任何客户端凭据。
我将问题分成了两个部分。API身份验证-是否来自已识别的实体(网站或本机应用程序)。API授权-该实体是否被允许使用此特定终点和HTTP动词。
授权是通过访问控制列表和用户权限以及在API代码、配置和数据库中设置的设置进行编码的。 API中的简单if语句可以测试授权并返回适当的响应(未经授权或处理API调用的结果)。
现在身份验证只是检查呼叫是否真实。为此,我向客户端发出自签名证书。他们随时可以从其服务器上调用API-通常在生成第一个页面时(或者在执行自己的应用程序登录检查时)。此调用使用我之前提供的证书。如果在我的一侧,我很高兴证书是有效的,那么我可以返回一次性号码和限时生成的API密钥。此密钥在后续调用其他API终点时使用,在承载头中,例如,它可以相当公开地存储在HTML表单字段或javascript变量或应用程序内的变量中。
一次性号码将防止重放攻击,如果有人想要窃取API密钥,则在过期后或在下一次调用之前更改一次性号码后,他们将无法继续使用。
每个API响应都将包含下一个一次性号码,如果一次性号码不匹配,则会返回身份验证错误。实际上,如果一次性号码不匹配,我也会取消API密钥。这将强制真正的API用户使用证书重新进行身份验证。
只要最终用户保持这些证书安全,并且不暴露他们用于进行初始身份验证呼叫的方法(例如,使其成为可以重放的ajax请求),那么API就非常安全。

您好,您的回答似乎是我想使用的东西,您知道任何可以指导我了解您所讲的内容的教程吗?谢谢。 - Costas Aletrari
@CostasAletrari 抱歉 - 我没有。我使用自定义代码在 Lumen 上完成了所有工作。 - PCaligari
1
这是一个很棒的想法..你如何解决来自不同浏览器标签页(不同进程)的多个并发请求的问题?此外,如果服务器发送的新nonce未能到达客户端(无论出于什么原因),你又如何解决这个问题 - 这两个问题都可能导致频繁的烦人的重新认证(取决于API密钥的生命周期)。有一篇博客文章与用户会话类似,它强调了这些问题并讨论了它们的解决方案:https://hackernoon.com/the-best-way-to-securely-manage-user-sessions-91f27eeef460 - Nemi Shah
前端应用程序不允许多个并发请求。利用本地存储和 Promises,您可以简单地将请求排队,以便它们必须等待上一个请求的新 nonce 才能继续。如果没有新的 nonce 返回(或不匹配),则重新认证请求在后台进行,用户不知道已经发生了这种情况。 - PCaligari

3

解决用户认证 API 的问题之一是在用户登录时从 API 请求认证令牌。此令牌随后可用于后续请求。您已经提到了这种方法 - 它非常可靠。

关于限制某些 Web 应用程序。您将希望每个 Web 应用程序在每个请求中标识自身,并在 API 实现内执行此身份验证。非常简单明了。


如果Web应用程序和其他应用程序都使用相同的API和身份验证机制,那么会有什么阻止某人A)窃取用户的身份验证令牌并在其他地方使用它以及B)冒充Web应用程序(或者,Web应用程序如何免于标识自己/防止人们假装成Web应用程序)? - maknz
@Dave,关于限制某些Web应用程序,您说这很简单,但是您能告诉我如何在Asp.Net Web API 2中实现吗? - martial
限制应用程序是我上述方法中证书的作用。没有证书,您无法获得令牌。没有令牌,您无法访问API套件的其余部分。通过使其时间有限并实施nonce检查,您可以阻止人们窃取令牌。 - PCaligari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接