在HTML5 Web App中使用OAuth2

我目前正在尝试使用OAuth2来开发一个完全用JavaScript构build的移动应用程序,这个应用程序可以与CakePHP API进行交stream。 看看下面的代码,看看我的应用程序目前的样子(请注意,这是一个实验,因此,代码混乱,缺乏结构的地区等)。

var access_token, refresh_token; var App = { init: function() { $(document).ready(function(){ Users.checkAuthenticated(); }); }(), splash: function() { var contentLogin = '<input id="Username" type="text"> <input id="Password" type="password"> <button id="login">Log in</button>'; $('#app').html(contentLogin); }, home: function() { var contentHome = '<h1>Welcome</h1> <a id="logout">Log out</a>'; $('#app').html(contentHome); } }; var Users = { init: function(){ $(document).ready(function() { $('#login').live('click', function(e){ e.preventDefault(); Users.login(); }); $('#logout').live('click', function(e){ e.preventDefault(); Users.logout(); }); }); }(), checkAuthenticated: function() { access_token = window.localStorage.getItem('access_token'); if( access_token == null ) { App.splash(); } else { Users.checkTokenValid(access_token); } }, checkTokenValid: function(access_token){ $.ajax({ type: 'GET', url: 'http://domain.com/api/oauth/userinfo', data: { access_token: access_token }, dataType: 'jsonp', success: function(data) { console.log('success'); if( data.error ) { refresh_token = window.localStorage.getItem('refresh_token'); if( refresh_token == null ) { App.splash(); } else { Users.refreshToken(refresh_token); } } else { App.home(); } }, error: function(a,b,c) { console.log('error'); console.log(a,b,c); refresh_token = window.localStorage.getItem('refresh_token'); if( refresh_token == null ) { App.splash(); } else { Users.refreshToken(refresh_token); } } }); }, refreshToken: function(refreshToken){ $.ajax({ type: 'GET', url: 'http://domain.com/api/oauth/token', data: { grant_type: 'refresh_token', refresh_token: refreshToken, client_id: 'NTEzN2FjNzZlYzU4ZGM2' }, dataType: 'jsonp', success: function(data) { if( data.error ) { alert(data.error); } else { window.localStorage.setItem('access_token', data.access_token); window.localStorage.setItem('refresh_token', data.refresh_token); access_token = window.localStorage.getItem('access_token'); refresh_token = window.localStorage.getItem('refresh_token'); App.home(); } }, error: function(a,b,c) { console.log(a,b,c); } }); }, login: function() { $.ajax({ type: 'GET', url: 'http://domain.com/api/oauth/token', data: { grant_type: 'password', username: $('#Username').val(), password: $('#Password').val(), client_id: 'NTEzN2FjNzZlYzU4ZGM2' }, dataType: 'jsonp', success: function(data) { if( data.error ) { alert(data.error); } else { window.localStorage.setItem('access_token', data.access_token); window.localStorage.setItem('refresh_token', data.refresh_token); access_token = window.localStorage.getItem('access_token'); refresh_token = window.localStorage.getItem('refresh_token'); App.home(); } }, error: function(a,b,c) { console.log(a,b,c); } }); }, logout: function() { localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); access_token = window.localStorage.getItem('access_token'); refresh_token = window.localStorage.getItem('refresh_token'); App.splash(); } }; 

我有许多与我的OAuth实现有关的问题:

1.)显然存储access_token在localStorage是不好的做法,我应该改为使用cookie。 谁能解释为什么? 由于这不再安全或不太安全,据我所知,因为cookie数据不会被encryption。

更新:根据这个问题: 本地存储vs Cookie存储在localStorage中的数据是只在客户端可用,不做任何HTTP请求不像cookie,所以似乎对我来说更安全,或似乎并不有任何问题,据我所知!

2.)关于问题1,使用cookie的过期时间,对我来说同样毫无意义,就像你看代码一样,在app start上发出一个请求来获取用户信息,如果这个信息会返回错误if它已经在服务器端过期了,并且需要一个refresh_token。 所以不能确定在客户端和服务器上都有到期时间的好处,当服务器是真正重要的时候。

3.)我如何得到一个没有A的刷新标记,并将其存储在原来的access_token中以供以后使用; B)还存储一个client_id? 我已经被告知这是一个安全问题,但我怎么才能稍后使用这些,但保护在一个只有JS的应用程序? 再次看到上面的代码,看看我迄今为止如何实现。

它看起来像你使用资源所有者密码凭据 OAuth 2.0stream,例如提交用户名/密码来获取访问令牌和刷新令牌。

  • 访问令牌可以暴露在JavaScript中,访问令牌被暴露的风险由于其较短的生命周期而被缓解。
  • 刷新令牌不应该暴露给客户端的JavaScript。 它被用来获得更多的访问令牌(正如你上面所做的那样),但是如果攻击者能够获得刷新令牌,他们将能够随意获得更多的访问令牌,直到OAuth服务器撤销授权为其刷新令牌的客户端。

考虑到这一背景,让我解决您的问题:

  1. cookie或localstorage会为您提供跨页刷新的本地持久性。 将访问令牌存储在本地存储中,可以提供更多针对CSRF攻击的保护,因为它不会像cookie一样自动发送到服务器。 您的客户端JavaScript将需要将其从本地存储中提取出来,并在每个请求上传输它。 我正在开发一个OAuth 2应用程序,因为这是一个单页的方法, 相反,我只是把它保存在内存中。
  2. 我同意…如果您存储在一个cookie中,只是为了持久性而不是过期,服务器将在令牌过期时响应一个错误。 我认为你可能会创build一个有效期的cookie的唯一原因是,你可以检测它是否已经过期,而没有先发出请求,并等待错误响应。 当然,通过保存已知的过期时间,您可以对本地存储执行相同的操作。
  3. 这是我相信的整个问题的症结所在……“我如何得到一个刷新标记,没有A,存储它与原来的access_token以后使用,和B)还存储一个client_id”。 不幸的是,你真的不能…正如在介绍性评论中指出的那样, 刷新令牌客户端会否定访问令牌的有限生命周期所提供的安全性。 我在我的应用程序(我没有使用任何持久的服务器端会话状态)所做的是:
    • 用户向服务器提交用户名和密码
    • 然后, 服务器将用户名和密码转发到OAuth端点,在http://domain.com/api/oauth/token上面的示例中,同时接收访问令牌和刷新令牌
    • 服务器将刷新标记进行encryption并将其设置为cookie(应该仅为HTTP)
    • 服务器仅以明文forms(在JSON响应中)和encryption的仅HTTP cookie来响应访问令牌
    • 客户端的JavaScript现在可以读取和使用访问令牌(存储在本地存储或任何其他
    • 当访问令牌到期时,客户端向服务器(而不是OAuth服务器,但是托pipe应用的服务器)向新令牌提交请求
    • 服务器接收它创build的encryption的仅HTTP cookie,解密它以获取刷新令牌 ,请求新的访问令牌并最终在响应中返回新的访问令牌

无可否认,这确实违反了你所寻找的“仅JS”限制。 然而,a)你真的不应该有一个JavaScript的刷新令牌和b)它需要在login/注销非常小的服务器端逻辑和没有持久的服务器端存储。

关于CSRF的说明 :正如评论中指出的那样,这个解决scheme没有提到跨站请求伪造 ; 请参阅OWASP CSRF预防备忘单,以获得有关解决这些攻击forms的更多想法。

另一种方法是根本不请求刷新令牌(不知道这是否与您正在处理的OAuth 2实现有关; 根据规范 ,刷新令牌是可选的 ),并在其到期时持续进行重新validation。

希望有所帮助!

完全安全的唯一方法是不存储访问令牌客户端。 任何具有(物理)访问权限的浏览器都可以获得您的令牌。

1)你的评估既不是一个很好的解决scheme是准确的。

2)如果仅限于客户端开发,使用到期时间将是最好的。 它不会要求你的用户频繁地重新authenticationOauth,并且保证令牌不会永远活着。 仍然不是最安全的。

3)获取新的令牌需要执行Oauth工作stream程来获得新的令牌。 client_id绑定到一个特定的域,Oauth可以运行。

保留Oauth令牌最安全的方法是服务器端的实现。

对于纯粹的客户端方法,如果有机会,尝试使用“隐式stream”而不是“资源所有者stream”。 作为响应的一部分,您不会收到刷新令牌。

  1. 当用户访问页面JavaScript检查localStorage中的access_token并检查expires_in
  2. 如果丢失或过期,则应用程序将打开新的选项卡,并将用户redirect到login页面,成功login后用户将被访问令牌redirect回访问令牌,该访问令牌仅由客户端处理,并保留在具有redirect页面的本地存储中
  3. 主页面可能在本地存储的访问令牌上有轮询机制,用户login后(redirect页面将令牌保存到存储)页面进程正常。

在上述方法中,访问令牌应该是长期居住的(例如1年)。 如果长时间存在令牌,可以使用以下技巧。

  1. 当用户访问页面JavaScript检查localStorage中的access_token并检查expires_in
  2. 如果丢失或过期,则应用程序打开隐藏的iframe并尝试login用户。 通常,auth网站有一个用户cookie,并将授权存储到客户端网站,因此login会自动发生,iframe中的脚本会将令牌填充到存储中
  3. 客户端的主页设置access_token和timeout的轮询机制。 如果在这个短的时间内access_token没有填充到存储器中,这意味着我们需要打开新的选项卡并设置正常的隐式stream动