自定义REST服务的API身份验证最佳实践



我会尽量保持这一点与框架无关,但我会参考我个人使用的框架来提供一些上下文。此外,我很抱歉,但这是一个很长的问题。

上下文:我在一个在线平台上工作。它由两个不同的服务器组成;后端";其服务于公共REST(ish)API(比如说API.example.com);前端";使用Sapper(Svelte)和ssr(example.com)。后者基本上为API客户端服务。

显然,有些端点与特定帐户相关,因此需要身份验证。据我所知,有两种不同的方法,它们有不同的漏洞:

  • 会话身份验证(cookie),易受CSRF攻击
  • 令牌身份验证(头),易受XSS攻击(通常令牌保存在localStorage中)

直接与后端服务器使用会话身份验证非常困难,因为页面由另一台服务器提供服务,生成/验证令牌即使不是不可能,也不是那么容易。

常见解决方案:人们似乎对使用localStorage感到非常焦虑,我之所以理解这一点,是因为攻击者可以使用XSS窃取令牌并冒充用户。因此,首选的解决方案似乎如下:

  • 将令牌存储在httpOnly cookie中
  • 每个请求都转到example.com/api/[…]
  • 这些端点充当"端点";"代理";,从cookie中获取令牌并将其放入Authorization头中,以将请求转发到API.example.com上的实际API

问题是,由于现在使用cookie进行身份验证(至少在"前端"级别),CSRF攻击再次成为问题。当然,现在处理它更容易了,因为这都是前端服务器的责任,而且已经有了用于express和polka的CSRF中间件。

然而,我的观点是,在存在XSS漏洞的情况下,CSRF令牌和CSRF保护通常是无用的,因此,即使JS无法直接访问令牌,平台仍然很脆弱。XSS保护仍然很关键,这样做我们一无所获。另一方面,现在前端服务器是";"忙";对于每个后端请求,它破坏了拥有不同服务器的优势之一。

我想做的基本上是坚持localStorage方法,但没有实际使用localStorage,以利用ssr。这个想法是,我可以使用Sapper中间件读取cookie并将令牌存储在Sapper会话中:

sapper.middleware({
session: (req, res) => ({
authorization: getCookie(req, 'supersecret') // get supersecret cookie from request
})
})

对于那些不了解Sapper的人来说,session在本文中基本上是一块由服务器初始化并与客户端共享的内存。制作cookie httpOnly并没有真正的作用,因为JS仍然可以使用会话存储访问该值。但是,由于服务器现在可以在页面重新加载期间访问令牌,我们现在可以利用ssr(在服务器上呈现从API获得的私有信息有多有用是有争议的,但为什么不呢?)。

问题是:这显然不是一种标准的方法,因此我不理解它可能导致(或已经存在)的问题,因为我似乎找不到任何负面影响。我做错了什么?这样做真的可以吗?

p.S.:;前端代理";如果我想使用API密钥并限制API的使用,这是强制性的,但我不认为我真的需要它(如果我错了,请纠正我)

所以你真正想做的是:

  1. 进行无状态身份验证和授权以提高的可扩展性

    您需要将会话数据存储在客户端上。您需要随每个请求发送会话数据的相关部分。您需要在会话数据中添加一个难以猜测的秘密,用于验证用户身份。您需要在服务器上为每个请求检查此机密。

  2. 通过XSS 避免会话被盗

    你至少需要将你的秘密转移到只使用HTTP的cookie中,这样就没有人可以用客户端上运行的javascript代码窃取它。

  3. 通过CSRF 避免会话使用

    您需要另一个难以猜测的秘密,它可以用于验证用户身份,或者对于实际会话是唯一的。客户端上运行的javascript代码可以知道这个秘密,并且必须以非cookie的方式随每个请求一起发送。您需要在服务器上为每个请求检查此机密,但如果使用应用程序需要运行客户端javascript代码,则无需在服务器上生成此机密。

实现这些功能的一种可能方法是将整个会话移动到由服务器创建和修改的加密签名JWT,并存储在客户端的仅HTTP cookie中。您可以将用户id、会话id和过期日期添加到此会话。您为每个请求验证其签名,并检查会话id是否不在黑名单上,或者会话是否未过期。您定期更新用于加密和签署JWT的密钥,例如每月更新一次。您也可以在HTTP头中发送加密的会话id,并将其与JWT中为每个请求获得的会话id进行比较。

从服务器的角度来看,一个不太安全、要求也不高的实现是仅将用户id、过期日期和会话id存储在仅签名但未加密的令牌中。其余的session可以存储在localstorage或sessionstorage中,客户端可以读取和修改它,而不仅仅是服务器。

一种更不安全的方式是在每次请求时只发送电子邮件和密码,而不是签名。

请注意,这只是我的意见,而不是一些行业标准,我建议使用现有的经过良好测试的产品,而不是使用您的自定义安全解决方案。

LocalStorage(或者MemoryStorage,如果你愿意的话)从来都不是一个好的生产解决方案。如果你只是在做一个演示或只展示一个更改,那么你应该坚持LocalStorage内存缓存。将Redis用于专用缓存服务。

这取决于您是否希望使用基于令牌的系统或基于会话的系统来确保服务器-客户端之间的一致性。您可以在请求中输入特定参数,以确保更好的身份(I.P.位置、更短的到期时间等)

设置https证书使XSS攻击的效果较差。利用服务器生成的令牌来确保基于会话的CSRF攻击更难执行(生成的令牌可以是不对称或对称类型)。

如果有疑问,请查看专家如何处理问题(除非您的方法新颖)。例如,银行的安全性很强,会话寿命很短,因此用户必须每x秒再次登录一次,因此,即使攻击者窃取了第一个会话密钥,在你重新验证后,他也不会拥有新的会话(假设他没有窃取新会话密钥的有争议的方法),这会增加一个障碍,但不是最终的防御措施,因为这并不存在。也许你也可以考虑使用私钥和公钥对的非对称加密,或者前端必须始终根据前端和后端都有的加密密钥生成新的密码短语的唯一代码生成器,你必须在每个请求中发送每个不断发展的密码,攻击者将无法通过这种安全措施,除非他能够访问前端代码,这对攻击者来说不是一种终极防御,而是一个额外的障碍。一种更理想的方法是使用机器学习来识别典型的用户行为模式,并标记出异常活动,如果当前活动会话中的置信度较低,则发送警报/重新验证。

最新更新