Javascript 中的安全 OAuth
Posted
技术标签:
【中文标题】Javascript 中的安全 OAuth【英文标题】:Secure OAuth in Javascript 【发布时间】:2011-09-02 22:37:00 【问题描述】:我有一个使用 OAuth 1.0a 对使用它的应用程序进行身份验证的 api。它正在替换一个旧的 api,该 api 使用了许多已被弃用的自定义构建和大杂烩调用。
众所周知,OAuth 1.0a 在(客户端)javascript 中是不安全的,因为它依赖于保密的消费者秘密。这是不可能的,因为源始终可见。
我们有 Chrome、Firefox、IE 和 Safari 的浏览器扩展,将来需要使用这个 api。这些扩展大部分或完全用 Javascript 编写,因此存在安全问题。
这些扩展是内部的,因此可以使用自定义身份验证方法来获取其访问令牌。
我计划实施的内容如下:
用户在浏览器中登录网站。 网站向他们发送带有会话密钥的 cookie。 然后我们的扩展程序获取该 cookie 并将其传递给 api。 api 验证它是一个有效且活动的会话,并向扩展发出其访问令牌。 这些令牌在过期前最多可以使用一小时。 对 javascript 发布的 cookie 的速率限制也将更低。它在以下假设下运行:
如果其他应用程序可以访问您的 cookie,那么他们无论如何都可以在网站上冒充您,因此访问 api 没有什么不同。 所有身份验证方法仍由我们控制。 令牌的定期到期意味着如果它们被泄露,那么利用的时间是有限的。我的问题是,这是一种限制对 api 访问的安全方法吗? 还有更好的吗?
一些笔记。 我知道 chrome 扩展可以请求访问给定站点的 cookie 的权限。我相信 Firefox 扩展也可以做到这一点。
显然,我们不希望通过任何页面上的 javascript 访问我们的 cookie,否则我们会将自己暴露于 XSS 攻击,因此只能通过扩展程序访问它们。
【问题讨论】:
嗯...在我看来,扩展方法本质上是不安全的,因为您的用户可以简单地创建自己的扩展或其他访问 api 的方法。 但他们仍然需要通过网站进行身份验证。如果他们可以从该站点获取 Cookie,则无论如何他们都可以访问该站点。所以我的方法只会和网站登录一样不安全。 如何将 OAuth 令牌存储在 LocalStorage 中? LocalStorage 只能由客户端浏览器读取,不会在 HTTP 请求标头中发送。 但这里的问题不在于存储令牌本身。它正在使用 api 进行身份验证以获取 OAuth 令牌。 啊,谢谢你的解释。 【参考方案1】:我编写了一个站点,该站点通过用于 OAuth 的 javascript 库进行 OAuth 登录。这是工作流程:
-
OAuth 仅在具有 LocalStorage 的浏览器上受支持
登录表单将检查 LocalStorage 中的 OAuth 密钥,并在存在 OAuth 密钥时自动尝试 OAuth 登录。
登录表单上有一个“记住我”复选框,因此用户可以在登录时为其创建 OAuth 令牌。
使用记住我的成功登录将:
查找或创建名称与 User Agent 相同的 ClientApplication,并在必要时创建令牌
在 html 响应中使用 javascript 标记进行响应。 javascript 标记将调用带有作为参数传递的标记的 javascript 函数。此函数会将 OAuth 令牌保存到 LocalStorage。
不成功的 OAuth 登录尝试将:
在 HTML 响应中使用 javascript 标记进行响应。 javascript 标记将调用 javascript 函数来清除 OAuth 令牌的 LocalStorage 设置。这将阻止额外的 OAuth 登录尝试
这个过程还有一些细节,如果你愿意,我可以告诉你更多。
【讨论】:
虽然这是一个很好的答案,但它并没有解决我的问题。我的问题是我分发代币的方法是否安全。它与存储它们无关。这是服务器端问题,而不是客户端问题。 您必须权衡安全性与可用性。就我而言,在生成消费者/请求/访问令牌时,可用性在一定程度上胜过安全性。【参考方案2】:所以你在 example.com 上有一个网站,它需要访问 api.com。您的扩展假定用户登录到 example.com,提取会话 cookie 并将其传递给 api.com 以获取 Oauth 令牌。听起来很合理,但有更简单的方法,无需编写浏览器插件。
在您的情况下,api.com 将与 example.com 通信以验证会话 cookie。这两个系统之间有很强的依赖性。 OAuth 通常用于 example.com 和 api.com 互不信任的情况。
由于这两个系统已经相互信任,您可以做各种事情来简化架构:
-
您可以创建一个托管在 example.com/api/* 上的代理来验证会话,然后盲目地转发到 api.com/*。就浏览器而言,没有跨域请求,所以一切正常。
您可以跨域使用联合登录。这比代理方法更复杂,但您可以轻松找到适合您平台的现有实现。
【讨论】:
除非我遗漏了什么,否则您无法使用 OAuth 1.0 进行客户端身份验证,因为您无法在客户端公开消费者机密。因此,代理解决方案听起来像是要走的路。不过,我是 OAuth 的新手,所以可能会遗漏一些东西。一些信息:quora.com/Is-it-safe-to-implement-OAuth-on-the-client-side【参考方案3】:对于之后发表这篇文章的人的一些想法:
“这是否安全” -> 这取决于我们要保护的威胁。我将在以下几点假设该解决方案已经暗示了受信任的网络链接(以防止在途令牌或凭据拦截尝试)。但是,描述中缺少一个关键元素,因为它没有提到我们是否保护 API 免受未经授权的用户(人类)或未经授权的 API 客户端(如在浏览器中运行的恶意扩展)的影响。前者可以通过可用的开放标准很容易地实现,而人们应该忘记试图阻止未经授权的扩展的访问,因为该模型从根本上依赖于开源客户端技术。这与工作站安全有关,而不是设计一个强大的身份验证/授权机制。我们仍然可以实现某种扩展身份验证机制,但对于知道如何阅读扩展源代码的人来说,这将毫无用处。
我们可以通过两种方式设计平台。通过检测 API 以允许查询身份验证服务。或者使用基于令牌的访问,其中 API 将在它收到的每个请求中请求存在一个有效令牌,而不是它的发射器。这意味着使用一个新角色扩展身份验证服务:API 票证发行者,这很少引起人们的兴趣。在阅读这个命题时,我们似乎正在通过将会话令牌转发到 API 来合并两个世界。 这是错误的。 首先,这不是设计基于 cookie 的会话令牌的目的。其次,它迫使 API 与用户认证服务的会话管理系统实现某种实时同步链接 -> 我们可以轻松避免耦合。
我假设主要目标是保护 API 免受未经授权的用户的攻击,并且我们不会尝试解决依赖本地系统访问的威胁。
现在,考虑到我们不会在 API 中实现身份验证逻辑,我们必须依赖用户对身份验证服务进行身份验证的模型,从而使任何底层扩展都能够请求访问令牌。
李>这将原来的场景修改如下:
用户使用浏览器登录网站。 网站发出一个包含会话密钥的 cookie。 扩展现在可以向身份验证服务发送票证请求。请求将包含会话令牌(默认浏览器行为),因此将被验证。 一旦扩展程序收到票证,扩展程序会将其转发到 API 并请求会话令牌。 API 通过询问会话管理器来验证票证。如果会话管理器说“是的,我制作了这张票并且它仍然有效”,API 会生成一个会话令牌并将其返回给扩展。此令牌将插入所有后续请求,而不是票证。这将避免会话管理器上的任何不必要的工作量。 令牌(不要将其与票证混淆)可以有一个非常短的生命周期,例如几分钟 -> 如果它过期,扩展只是返回到身份验证服务并请求新票证(返回步骤 3以上)。上述解决方案基本上依赖于票证和令牌的安全性。他们的设计至少必须针对以下 5 个剩余威胁实施对策:i)尝试猜测票证/令牌(足够安全的随机生成),ii)尝试计算票证/令牌(足够大的熵),iii)尝试重用票证/令牌(到期),iv) 尝试篡改有效票证/令牌(完整性检查),v) 尝试在没有有效令牌/票证的情况下访问 API(验证 API 收到的每个请求中的令牌)。
这种方法的另一个优点是我们可以通过发出扩展特定令牌来优化资源分配,这反过来会触发 API 上的特定逻辑(减少 API 访问、减少生命周期、请求限制等)。
希望对你有帮助。
【讨论】:
以上是关于Javascript 中的安全 OAuth的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript AJAX 调用中的安全 REST API 令牌