如何使用REST API进行身份validation? (浏览器+本机客户端)

我正在使用Rails构build一个Web应用程序。 目前,我正在使用HTTP会话的devise很容易设置,它运作良好。

该应用程序由一个提供AJAX Web应用程序的URL组成。 其余的可用URL属于REST API。 所以,一切和每一个小数据请求都是通过AJAX完成的。

现在我想扩展整个事情来支持本地客户端。 我读了很多关于无状态身份validation,http基本和摘要auth,http会话,cookie,xsrf等等。现在我觉得我不能有一个安全的应用程序,因为总有办法劫持它的一些部分。

1 .: HTTP会话比。 无状态身份validation令牌

有什么不同? 我不明白。

  • HTTP会话:

    1. 客户端请求一个URL(第一个请求到服务器)
    2. 服务器给出正常的响应加上一些唯一的string(==会话ID)
    3. 客户端必须发送这个string与每个请求(这是使用HTTP标头自动完成)
    4. 客户端login – >服务器记住这个特定的会话ID现在login
    5. 客户端访问一个页面,需要auth – >没有特别的事情做,因为会话ID将自动通过HTTP头部发送到服务器
  • 无状态身份validation令牌:

    1. 客户端请求URL(对服务器的第一个请求)
    2. 服务器只是给出正常的响应, 没有任何密钥或令牌或ID
    3. (这里没什么特别的)
    4. 客户端login – >服务器创build一个授权令牌,并将该令牌发送给响应中的客户端
    5. 客户端访问页面,需要身份validation – >客户端必须提交身份validation令牌

对我来说这两种方式看起来都很相似 使用Rails,我还可以select将会话存储在数据库中… Devise将使用无状态身份validation令牌。

2:authentication方法

现在我用{"user":{"email":"e@mail.com","password":"p455w0rd"}}使用POST /users/sign_in

但是还有其他的可能性,比如HTTP基本authentication和HTTP摘要authentication,还有像oAuth这样的解决scheme(对我来说太大了)。

从我读过的内容来看:

  • 关于sign_in安全性,当前的POST /users/sign_in和HTTP基本authentication之间没有区别。 两者都使用明文。
  • 对于sign_out HTTP基本身份validation有一个缺点:退出只能closures浏览器窗口
  • HTTP摘要authentication有一个巨大的优势:它根本不传输密码(只是密码随机生成的string散列)
  • (德文)维基百科说:所有浏览器都不支持HTTP摘要authentication。 也许这个信息是老路?

我需要的:

  • 用户名和哈希密码(bcrypt)存储在数据库中。
  • 用户可以改变他的密码,密码也不会以明文forms发送。 (当涉及用户sign_up时,会发生同样的问题)。 可能的解决scheme?
    1. 当然:使用SSL / TLS
    2. 客户端请求一个want_to_change_password_salt并使用它来encryption客户端的密码。 但是 (?!)通过这种方式,我发送了散列密码的重要部分加上散列密码。 听起来对我不安全?

3 .: CSRF令牌

如上所述,现在我只有一个正常的AJAX网站使用REST API。 它具有XSRF保护:网站通过导轨交付,因此embedded了XSRF令牌。 我使用AJAX读取它,并在进行POST时传输它。 然后,Rails返回请求的数据和一个新的XSRF标记,然后将它用于下一个POST

现在我想更改我的服务器应用程序以使用本机客户端。 本地客户端不会加载HTML页面,因此不会检索CSRF令牌。 所以下面的选项出现在我的脑海里:

  • 创build一个XSRF令牌REST资源。 因此,(本地)客户端必须先从该资源请求一个XSRF令牌,然后才能执行第一个POST
  • 完全禁用XSRF保护。

问题:

  • XSRF保护如何工作(在Rails中)? 服务器如何知道哪个令牌属于哪个客户端? 我能想到的唯一方法就是会话。 这个假设导致:
  • 如果为了创build完全无状态的REST API而禁用会话,则XSRF保护将不再起作用。 对?

4:无状态身份validation令牌

这里我主要有很多问题:

  • 它是否具有与HTTP会话相同的安全问题? 我的意思是:窃取会话ID与窃取身份validation令牌的效果相同。 对?
  • 身份validation令牌的有效期应与HTTP会话一样:服务器必须在某处(数据库和会话)存储一个时间戳并检查。
  • sign_out的工作原理也一样吗?
    • 会话:销毁服务器上的会话
    • 身份validation令牌:销毁服务器上的令牌
  • 从我读到的应该是更安全的存储身份validation令牌HTTP头(就像会话ID),因为服务器日志可以包含GET参数,因此可以包含令牌。
  • 它应该只是一个简单的authentication令牌,或者如果客户端也发送它的user_id甚至散列的密码会更好吗? 我在这里读到,客户应该发送:
    1. user_id
    2. expiration_date
    3. [ user_idexpiration_dateSECRET_KEY ]的散列(或HMAC?)。 SECRET_KEY基本上是由服务器生成的一个随机string。

对于huuge的post感到抱歉,但是安全至关重要! 而且我不想犯可能暴露私人数据的devise错误。

谢谢 :)


这里有一些新的信息和新的问题;-)

5:本地客户

就本土客户而言,没有( 简单 )使用会话的方式:

  • 本地客户端不是浏览器

  • 因此,它不会轻易处理cookies(没有cookies没有典型的会话处理)

所以有三种可能的select:

  1. 实施本地客户端的会话处理。 这将是:

    1. login
    2. 读取响应的HTTP标题以获取cookie
    3. 保存所有你需要的cookie数据(特别是会话内容)
    4. 发送此会话ID与您做的每个请求
  2. 根本不要使用会话。 从本地客户的angular度来看,它几乎与1相同:

    1. login
    2. 从HTTP头或响应正文获取一些身份validation令牌(这是您的应用,尽pipe取决于您)
    3. 在本地保存这个令牌
    4. 发送此令牌与每个请求
  3. 混合的方法。 这基本上意味着服务器必须区分浏览器和本地客户端,然后检查提供的会话ID和会话数据,或者(对于本地客户端)检查提供的身份validation令牌。

6 .: CSRF令牌无状态(=无会话/无Cookie)授权

CSRF保护可以保护您的用户免受恶意网站的攻击,这些恶意网站会尝试以您login的用户的名义对您的API做出一些请求,但是用户不知道这一点。 使用会话时非常简单:

  1. 用户login您的API
  2. 会话获得创build
  3. 您的用户浏览器将使用此会话ID设置Cookie
  4. 用户执行的每个请求都会自动进行身份validation,因为浏览器会将所有Cookie(包括会话ID)以及每个请求发送到您的API

因此,攻击网站只需要做到以下几点:

  1. 编写一个指向你的API的自定义HTML <form>
  2. 让用户以某种方式点击Submitbutton

当然这个表格会是这样的:

 <form action="http://your.api.com/transferMoney" method="post"> <input type="hidden" name="receiver" value="ownerOfTheEvilSite" /> <input type="hidden" name="amount" value="1000.00" /> <input type="submit" value="WIN MONEY!!" /> </form> 

这导致以下假设

  1. CSRF保护只是因为浏览器自动发送cookie。

  2. 本地客户端不需要CSRF保护(当然,您的浏览器无法访问您的本机应用程序的身份validation数据(令牌,cookie,无论),而您的本机应用程序将不会使用浏览器与API进行通信)

  3. 如果你有一个不使用Cookies来validation用户的APIdevise,那么不可能做CSRF。 因为攻击者必须知道身份validation令牌并将其与恶意请求一起明确发送。

如果你想保护你的应用程序,你当然可以使用CSRF令牌和你无状态的身份validation机制,但我敢肯定,没有额外的安全增益。


7 .:正确的HTTP方法来select

login/login和注销/登出:

切勿使用GET (至less)三个原因:

  1. 在大多数情况下,CSRF保护只保护POST,PUT,PATCH和DELETE,因此CSRF可以在不知情的情况下使用GET请求login用户

  2. GET请求不应该改变应用程序状态。 但是,即使用会话应用程序状态更改login/注销,因为会话被创build或销毁。

  3. 当使用GET请求并将身份validation信息作为URL参数(即http://your.api.com/login?username=foo&password=bar )发送时,还有另一个问题:服务器日志! 大多数服务器只是logging每个包括所有URL参数的HTTP请求。 这意味着:如果你的服务器遭到黑客入侵,就不需要从你的数据库中破解密码哈希,他们只需要查看服务器的日志文件即可。 另外,恶意pipe理员还可以读取每个用户的login信息。 解决scheme:

    • 使用POST(或者你喜欢的任何方法)并在请求体内发送authentication信息。 要么:
    • 在HTTP标头中发送authentication信息。 因为这些信息通常不会出现在服务器日志文件中。 要么:
    • 看看服务器configuration,并告诉它删除名为“密码”(或混淆,所以URL变成login?username=foo&password=***内的日志)的每个URL参数。 但是我build议,简单地使用请求主体和POST方法一起使用这种信息。

所以你可以使用例如:

POST http://your.api.com/authenticationlogin

DELETE http://your.api.com/authentication注销


8:密码和散列

身份validation只适用于一些密钥。 当然,这个密钥应该保密。 意即:

  • 切勿以明文forms在数据库中存储密码。 有几个库可用来保证安全。 在我看来,最好的select是bcrypt

  • bcrypt :已经优化了哈希密码。 它会自动生成一个salt并多次散列密码(轮次)。 此外,生成的哈希string包含所需的一切:循环次数,盐和散列。 虽然你只需要存储这一个string,没有必要手写任何东西。

  • 当然你也可以使用其他强大的哈希库。 但是对于大多数人来说,你必须实施腌制,自己使用超过1轮。 此外,他们不会像bcrypt那样给你一个单一的string,尽pipe你必须pipe理自己存储轮次,盐和散列,然后重新组装。

  • 轮次 :这只是密码被散列的频率。 当使用5000轮时,散列函数将返回密码散列哈希散列的散列 。 这样做基本上有一个原因:它耗费CPU电源! 这意味着:当有人试图强制你的哈希值时,使用5000轮时需要5000倍的时间。 对于您的应用程序本身,这并不重要:如果用户知道他的密码,他将不会识别,如果服务器需要0.0004ms或2ms来validation它。

  • 好的密码 :如果密码太简单,最好的散列函数是没用的。 如果可以破解的话,用一本字典,如果用5000轮的话就可以了,这可能需要几个小时的时间,但是几个小时呢,如果可能要几个月或者几年呢? 虽然请确保您的用户密码包含通常的build议(低+大写+数字+特殊字符等)。


9 .:通过电线发送encryption密码

如果您不能(或不想)依赖HTTPS,但不希望在login时以明文方式发送密码,则可以使用非对称encryption( http://en.wikipedia.org/wiki/Public -key_cryptography )。

这个服务器创build一个密钥对(公钥和私钥)。 公钥被提供给客户,私钥必须保密!

客户端现在可以使用公钥对数据进行encryption,并且只能由私钥所有者(=服务器)来解密这些数据。

这不应该(!)用来存储数据库中的密码,因为如果你的服务器被黑客攻击,黑客将拥有encryption的密码私钥用于解密。 尽pipe继续使用一些散列algorithm(如bcrypt)来存储数据库中的密码。 另一个原因是,如果你认为有人破解你的encryption,你可以轻松地生成一个新的密钥对。

HTTPS基本上以相同的方式工作。 但是,如果您的应用程序使用HTTPS(推荐),则在安全性方面可能没有太大的好处。 但是,如上所述,如果您不能以任何理由使用HTTPS,或者不信任它,那么这就是构build您自己的安全连接的一种方法。

请记住,一个真正的HTTPS连接将encryption整个(!)连接和所有数据,而不仅仅是密码数据。 它从客户端到服务器端,服务器端到客户端都进行encryption。