REST API 中基于令牌的身份验证

Posted

技术标签:

【中文标题】REST API 中基于令牌的身份验证【英文标题】:Token based authentication in REST APIs 【发布时间】:2016-07-16 09:28:43 【问题描述】:

我尝试实现基于令牌的身份验证方法:

    每次成功登录都会创建新令牌。

    如果用户选择“让我保持登录”或用户正在使用移动设备,则令牌将保留在 Redis 数据库中,没有到期日期。否则,令牌将在 20 分钟后过期。

    一旦用户通过身份验证,就会从我的 Redis 数据库中的每个后续请求中检查令牌。

我想知道如何识别设备。对于移动设备,我可以使用设备标识符。但是如何识别浏览器呢?

示例:用户使用 Chrome 登录并选择“保持登录”。一个令牌被生成并与 Redis 中的浏览器名称一起保存。如果用户从 Firefox 登录,则将令牌和“Firefox”保存在数据库中。我将令牌保存在 Redis 中,而令牌是在成功验证时创建的。只保留令牌和使用令牌的浏览器是否可以?还是我也需要保留 IP?

补充问题:如何避免攻击者从 cookie 中窃取令牌?

【问题讨论】:

回顾一下,您需要确定请求来自哪个设备类型?您需要“验证”设备类型吗? 【参考方案1】:

基于令牌的身份验证如何工作

简而言之,基于令牌的身份验证方案遵循以下步骤:

    客户端将其凭据(用户名和密码)发送到服务器。 服务器对凭据进行身份验证并生成令牌。 服务器将先前生成的令牌连同用户标识符和到期日期一起存储在某个存储中。 服务器将生成的令牌发送给客户端。 在每个请求中,客户端都会将令牌发送到服务器。 服务器在每个请求中从传入请求中提取令牌。使用令牌,服务器查找用户详细信息以执行身份验证和授权。
      如果令牌有效,则服务器接受请求。 如果令牌无效,服务器拒绝请求。
    服务器可以提供一个端点来刷新令牌。

如何向服务器发送凭据

在 REST 应用程序中,从客户端到服务器的每个请求都必须包含服务器能够理解的所有必要信息。有了它,您就不会依赖存储在服务器上的任何会话上下文,也不会破坏 Roy T. Fielding 在其dissertation 中定义的 REST 架构的stateless constraint:

5.1.3 Stateless

[...] 从客户端到服务器的每个请求都必须包含理解请求所需的所有信息,并且不能利用服务器上存储的任何上下文。因此,会话状态完全保留在客户端上。 [...]

当访问需要身份验证的受保护资源时,每个请求都必须包含所有必要的数据才能正确地进行身份验证/授权。这意味着将对每个请求进行身份验证

请查看RFC 7235 中关于新身份验证方案注意事项的引用:

5.1.2. Considerations for New Authentication Schemes

HTTP 身份验证框架的某些方面 限制新的身份验证方案如何工作:

假定 HTTP 身份验证是无状态的:所有 必须提供验证请求所需的信息 在请求中,而不是依赖于服务器记住 先前的请求。 [...]

并且身份验证数据(凭据)应属于标准 HTTP Authorization 标头。来自RFC 7235:

4.2. Authorization

Authorization 标头字段允许用户代理进行身份验证 本身与原始服务器 - 通常,但不一定,之后 收到401(未经授权)响应。它的价值包括 包含用户身份验证信息的凭据 被请求资源领域的代理。

Authorization = credentials

[...]

请注意,这个 HTTP 标头的名称是不幸的,因为它携带 身份验证 数据而不是 授权。无论如何,这是发送凭证的标准标头。

在执行基于令牌的身份验证时,令牌是您的凭据。在这种方法中,您的硬凭证(用户名和密码)将交换为在每个请求中发送的令牌。

令牌是什么样子的

身份验证令牌是由服务器生成的用于识别用户的一段数据。基本上,标记可以是不透明的(除了值本身之外不显示任何细节,例如随机字符串)或者可以是自包含的(像 JSON Web Token):

随机字符串:可以通过生成随机字符串并将其保存到具有到期日期和与之关联的用户标识符的数据库中来颁发令牌。

JSON Web Token (JWT):由RFC 7519 定义,是在两方之间安全地表示声明的标准方法。 JWT 是一个自包含的令牌,使您能够将用户标识符、到期日期和任何您想要的(但不存储密码)存储在有效负载中,这是一个编码为JSON Base64。客户端可以读取有效负载,并且可以通过在服务器上验证其签名来轻松检查令牌的完整性。如果您不需要跟踪 JWT 令牌,则无需保留它们。尽管如此,通过持久化令牌,您将有可能使它们失效并撤销它们的访问权限。要跟踪 JWT 令牌,而不是保留整个令牌,您可以根据需要保留令牌标识符(jti 声明)和一些元数据(您为其颁发令牌的用户、到期日期等)。要找到一些与 JWT 合作的优秀资源,请查看 http://jwt.io。

提示:始终考虑删除旧令牌,以防止您的数据库无限增长。

如何接受令牌

您应该从不接受过期的令牌或不是由您的应用程序发布的令牌。如果您使用的是 JWT,则必须检查令牌签名。

请注意,一旦您发出令牌并将其提供给您的客户,您就无法控制客户将如何处理该令牌。 没有控制说真的

检查User-Agent 标头字段以判断正在使用哪个浏览器访问您的API 是一种常见的做法。然而,值得一提的是,HTTP 标头可能很容易被欺骗,您应该永远不要相信您的客户端。浏览器没有唯一标识符,但如果你愿意,你可以得到很好的fingerprinting。

我不知道您的安全要求,但您始终可以在您的服务器中尝试以下操作以增强您的 API 的安全性:

检查颁发令牌时用户使用的浏览器。如果以下请求中浏览器不同,直接拒绝token即可。 在颁发令牌时获取客户端远程地址(即客户端 IP 地址),并使用第三方 API 查找客户端位置。例如,如果以下请求来自其他国家/地区的地址,则拒绝该令牌。要通过 IP 地址查找位置,您可以尝试免费的 API,例如 MaxMind GeoLite2 或 IPInfoDB。请注意,为您的 API 收到的每个请求访问第三方 API 并不是一个好主意,并且可能会对性能造成严重损害。但是您可以通过存储客户端远程地址及其位置来最小化 缓存 的影响。现在有一些缓存引擎可用。仅举几例:Guava、Infinispan、Ehcache 和 Spring。

通过网络发送敏感数据时,您最好的朋友是 HTTPS,它可以保护您的应用程序免受 man-in-the-middle attack 的攻击。

顺便说一句,我有没有提到你永远不应该信任你的客户?

【讨论】:

我只是担心在每个请求中我需要调用第三方服务的额外工作量。 @PrashantThorat 使用缓存。这就是缓存的用途。 我已经在使用 Redis 在内存令牌中进行身份验证。我说的是来自 IP 的位置,在这种情况下我需要第三方 API。 @PrashantThorat 不幸的是,安全性有一些权衡。 处理它。但是,为了将收到的每个请求访问第三方 API 的影响降到最低,为什么不使用缓存来存储 IP 和位置呢?我正是这个意思。顺便说一句,MaxMind GeoLite2 是一个可下载的数据库。 是的,我将位置和 IP 与令牌一起存储在 Redis 中,但每次 API 调用从第三方或我们的数据库获取客户端位置然后在缓存中进行比较是复杂的操作【参考方案2】:

一旦服务器接收到来自客户端的请求,它就会包含 User-Agent。该属性将帮助我们识别客户。

请参考此链接:How do I detect what browser is used to access my site?

【讨论】:

以上是关于REST API 中基于令牌的身份验证的主要内容,如果未能解决你的问题,请参考以下文章

Netsuite - REST API - 使用基于令牌的身份验证 (TBA) 进行查询 - (在 Python 中)

Netsuite - REST API - 如何使用基于令牌的身份验证 (TBA) 创建新条目记录 - (在 Python 中)

Django Rest 框架中的基于会话的与令牌身份验证

带有 jwt 身份验证的 django rest api 要求 csrf 令牌

django rest 框架身份验证

REST API 身份验证令牌