RESTful Web服务认证

43

我有一个Restful Web服务API,被不同的第三方使用。该API的一部分是受限制的(需要用户名/密码才能访问)。我想知道最好的实现身份验证的方法是什么?

我正在使用https,因此通信是加密的。我有两个想法:

  • 用户在开始使用(受限制的)服务之前,使用POST发送用户名/密码(由于使用了https,凭据已加密)。登录成功后,服务器会发送回与此用户名匹配的随机单次使用值(nonce)。当下一个请求正在进行时,在用户名旁边,客户端发送先前返回的nonce。服务器匹配用户名和nonce,并在所请求的数据旁边返回新的nonce。每个新请求使用新的nonce。基本上,这是Digest访问身份验证的轻量级版本。
  • 由于第三方使用此API,因此用户名/密码可以用于每个(受限制的)请求。由于使用了https,它们将被加密。此方法的缺点是,这不符合Restful规范(始终将使用POST)。

我更倾向于选择第一种方法(它符合Restful规范,相对容易实现,XML、JSON或HTML可以在不改变任何内容的情况下使用),但我想知道您的意见。您推荐哪一个:第一种、第二种还是第三种方法?

顺便说一下,我在服务器端使用Python。

4个回答

22

我曾经在API中看到过一种方法(也是我目前正在实现的方法),就是创建一个名为 Session 的RESTful资源,通过 POST 提供用户名和密码来创建它。

这是我基本上的实现方式:

POST /sessions { Username: "User", Password: "Password" }
创建一个有时限的会话并返回包含会话密钥值和过期时间的会话资源。您可能还想将其作为 cookie 值返回,以方便 API 客户端的实现。
DELETE /session/{id}

立即使会话过期,以便它不能再被使用。这可用于明确的注销。

然后,我让用户通过查询参数附加会话键,尽管您也可以允许其通过 cookie 值提交,我建议两种都允许。

我喜欢这个方法的原因是它非常简单。

显然,你的情况会在一定程度上决定如何管理会话,也许它们不受时间限制并且持续无限期,也许它们被哈希或加密以增加安全性。

如果您在所有地方都使用 HTTPS,则可能不需要太担心。但是,如果您想使用 HTTP,则需要像哈希一样使用秘密键和时间戳来生成每个请求的安全键。这样,您可以通过 HTTPS 共享密钥,然后切换到 HTTP 进行进一步的调用。即使有人从请求中嗅探出密钥,它也会很快过期并且变得无用。

免责声明:我不是安全专家 ;-)。


1
如果您在服务器上使用会话,那么您的API就不是RESTful的,因为服务器具有状态。每个请求都应包含所有必要的数据,服务器不应依赖于会话。将会话数据保存在JSON Web令牌中,客户端在每个请求的标头中发送该令牌。例如,您可以在其中打包序列化的用户对象(这就是我所做的)。 - felixfbecker
这似乎有点迂腐,而且假设严格遵循“RESTful”对身份验证很重要。不过,如果您的应用程序需要或者重视以这种方式处理事情,像JWT这样的方法确实可以解决这个问题。 - Chris Nicola

8

在这里,没有理由不使用HTTP身份验证。

尽管如此,通过POST获取时间块随机数的概念可以很好地工作。但首先需要了解为什么需要跳过这个额外的步骤。

当使用bcrypt哈希原始密码时,考虑使用这种技术,因为验证用户的实际成本很高(如果您不知道,bcrypt可以调整为花费相当多的实时来执行哈希函数)。选择提供一次“登录”服务,使用将通过bcrypt进行昂贵验证过程的密码,然后会得到一个时间阻止标记以换取未来请求,这些请求将绕过bcrypt过程。

对于bcrypt过程,使用HTTP身份验证,服务将使用普通密码和令牌一起工作。这样,用户始终可以使用其服务的密码,但价格昂贵。所以他们可以这样做,只是不应该这样做。服务不关心客户端使用哪种身份验证技术。

随机数服务被提供作为改进吞吐量的附带服务。

除此之外,这是标准的HTTP身份验证,但有一个新的方案。


1
不使用HTTP身份验证的一个原因是浏览器会自动发送每个请求,从而使您暴露于CSRF攻击。使用自定义授权方案是可以的。或者使用自定义标头。 - Dobes Vandermeer

6

通过任何标准衡量都是最佳答案。如果您能在这里对算法本身进行良好的介绍,我相信您很快就会获得大量的赞成票!我认为,最重要的一点是:“您首先使用秘密访问密钥创建签名密钥。签名密钥适用于特定区域和服务。此外,签名密钥在创建后七天后过期。由于签名密钥的范围和生命周期受到限制,因此如果签名密钥被泄露,您的数据风险较小。” - Domi

4

假设该服务永远不会在浏览器中使用,且通信已经加密,我认为采用第二种方法的变体不会有任何危害:添加X-Headers以在每个请求中发送用户名/密码,例如:

GET /foo HTTP/1.1
Host: www.bar.com
X-MyUsername: foo
X-MyPassword: bar

另一个想法是使用HTTP基本认证,只需发送一个Authorization: Basic base64(user:password)头部信息。前提是连接始终加密。

在这种情况下,你如何实现“登录”和“注销”? - Bharat Khatri
2
我认为在这种情况下没有“登录”或“注销”,因为您在每个请求中发送用户名/密码。 - Cacho Santa
基本身份验证由于跨站点请求伪造问题仍然不够安全。 - Dobes Vandermeer
1
你绝对不想发送未加密的永久密码。作为一个基本的经验法则,你甚至想要最小化重新发送加密密码,并使用临时令牌(就像会话密钥)代替。 - Domi

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