如何使用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会话:
- 客户端请求一个URL(第一个请求到服务器)
- 服务器给出正常的响应加上一些唯一的string(==会话ID)
- 客户端必须发送这个string与每个请求(这是使用HTTP标头自动完成)
- 客户端login – >服务器记住这个特定的会话ID现在login
- 客户端访问一个页面,需要auth – >没有特别的事情做,因为会话ID将自动通过HTTP头部发送到服务器
-
无状态身份validation令牌:
- 客户端请求URL(对服务器的第一个请求)
- 服务器只是给出正常的响应, 没有任何密钥或令牌或ID
- (这里没什么特别的)
- 客户端login – >服务器创build一个授权令牌,并将该令牌发送给响应中的客户端
- 客户端访问页面,需要身份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?
- 当然:使用SSL / TLS
- 客户端请求一个
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甚至散列的密码会更好吗? 我在这里读到,客户应该发送:
-
user_id
-
expiration_date
- [
user_id
,expiration_date
,SECRET_KEY
]的散列(或HMAC?)。SECRET_KEY
基本上是由服务器生成的一个随机string。
-
对于huuge的post感到抱歉,但是安全至关重要! 而且我不想犯可能暴露私人数据的devise错误。
谢谢 :)
这里有一些新的信息和新的问题;-)
:
5:本地客户
就本土客户而言,没有( 简单 )使用会话的方式:
-
本地客户端不是浏览器
-
因此,它不会轻易处理cookies(没有cookies没有典型的会话处理)
所以有三种可能的select:
-
实施本地客户端的会话处理。 这将是:
- login
- 读取响应的HTTP标题以获取cookie
- 保存所有你需要的cookie数据(特别是会话内容)
- 发送此会话ID与您做的每个请求
-
根本不要使用会话。 从本地客户的angular度来看,它几乎与1相同:
- login
- 从HTTP头或响应正文获取一些身份validation令牌(这是您的应用,尽pipe取决于您)
- 在本地保存这个令牌
- 发送此令牌与每个请求
-
混合的方法。 这基本上意味着服务器必须区分浏览器和本地客户端,然后检查提供的会话ID和会话数据,或者(对于本地客户端)检查提供的身份validation令牌。
6 .: CSRF令牌无状态(=无会话/无Cookie)授权
CSRF保护可以保护您的用户免受恶意网站的攻击,这些恶意网站会尝试以您login的用户的名义对您的API做出一些请求,但是用户不知道这一点。 使用会话时非常简单:
- 用户login您的API
- 会话获得创build
- 您的用户浏览器将使用此会话ID设置Cookie
- 用户执行的每个请求都会自动进行身份validation,因为浏览器会将所有Cookie(包括会话ID)以及每个请求发送到您的API
因此,攻击网站只需要做到以下几点:
- 编写一个指向你的API的自定义HTML
<form>
- 让用户以某种方式点击
Submit
button
当然这个表格会是这样的:
<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>
这导致以下假设 :
-
CSRF保护只是因为浏览器自动发送cookie。
-
本地客户端不需要CSRF保护(当然,您的浏览器无法访问您的本机应用程序的身份validation数据(令牌,cookie,无论),而您的本机应用程序将不会使用浏览器与API进行通信)
-
如果你有一个不使用Cookies来validation用户的APIdevise,那么不可能做CSRF。 因为攻击者必须知道身份validation令牌并将其与恶意请求一起明确发送。
如果你想保护你的应用程序,你当然可以使用CSRF令牌和你无状态的身份validation机制,但我敢肯定,没有额外的安全增益。
7 .:正确的HTTP方法来select
login/login和注销/登出:
切勿使用GET
(至less)三个原因:
-
在大多数情况下,CSRF保护只保护POST,PUT,PATCH和DELETE,因此CSRF可以在不知情的情况下使用GET请求login用户
-
GET请求不应该改变应用程序状态。 但是,即使用会话应用程序状态更改login/注销,因为会话被创build或销毁。
-
当使用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/authentication
login
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。