但是对于 Web Browser 来说, client id 和 secret 要存在浏览器中, 似乎这很不安全. (但是 Google 并没有反对这么做: https://stackoverflow.com/a/39625963, https://stackoverflow.com/a/2256312)
有一种后端的通信方式可以尝试: app 并不直接与 Resource Server 的 API 交互, 而是通过一个 proxy server. 对于 auth server 和 resource server 来说, 存储着 client id 和 secret 的 proxy server 就是一个授权的 client, app 与 proxy server 的通信也能避免跨域问题, 但是否有潜在问题有待进一步验证.
在资源访问上,
存在 website 专属 API 和开发者 REST API, 二者的调用方式不同.
前者只需提交登录后保存在 cookies 中的 auth_token 即可.
那么 Twitter 是如何解决上述问题?
通过 chrome dev-tool 可以发现, 每次登录时向一个接口 /sessions 发起 POST 请求, 携带的 body 除了 username 和 password 之外, 还有一个 authenticity_token, 但是这个 Token 不是 access_token, 而是用作防止 CSRF 的 校验 token, 这个 token 由 server 生成并包含在只有通过加载 HTML 的方式取得:
这种数据通常是表单中的一个数据项。服务器将其生成并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过CSRF传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验token的值为空或者错误,拒绝这个可疑请求。--- Wikipedia
因此, 除非首先访问 Twitter 登录页, 然后提取出 token. 这也为访问这些 website API 增加了障碍.
Conclusion
很遗憾目前没有发现符合最初调研目地的方案, 但已经尽力而为的去做了相关的调查, 或许那种理想中的方案本身并不是一个 best practice.
Intro
本文从仅一个非 Web 开发者的角度出发, 探讨 “如何设计出安全的 API 给授权的第三方 app 及自身 app 调用”. 安全性, 指仅仅允许注册过的 app 在授权后访问指定 scope 的 API. 如何提供这种安全性, OAuth RFC 中有详细的描述.
Problems
安全性的前提是没有问题的. 但我们面临的问题是前后端分离模式下的安全性与应用使用体验上的冲突.
当我们为第三方提供了应用注册和 OAuth 2.0 授权机制后, 显然三方应用可以通过 redirect 完成 RFC 里描述的认证流程了. 但是对于 Resource Server 对应的原生 app, 这种登录方式可能显得不是那么友好, 我们希望更无缝的登录体验. 于是我们以此为调研出发点, 寻求一种解决方案.
Solution
同一个 Resource Server, 如何做到既能保证第三方应用的授权必须遵循 OAuth 2.0 best practices, 并且原生应用可以绕过复杂的授权而做到自身服务的登录呢?
Fig 1
首先考虑如果所有 app 都遵循标准的 OAuth 2.0 规范, 能否做到? 这样, 问题变成 “如何让原生客户端 app 在不发生页面/应用跳转的情况下完成登录”. 如果客户端中直接允许用户(Resource Owner)输入 Auth Server 的用户名和密码的话, 那么确实可以做到直接登录, 然而这违反了 OAuth 的 Best Practice: 在第三方中存储用户隐私凭据是不安全的. 剩余的 Authentication grant 方式都是需要 redirect 的, 因此这个问题没找到合理的解决方案.
但作为开发者我们知道原生应用一定是免授权范畴的, 因此上述问题可以演化为 “如何区分一类特殊的客户端 app (原生 app), 只允许它们直接使用用户名密码登录”.
但是对于 Web Browser 来说, client id 和 secret 要存在浏览器中, 似乎这很不安全. (但是 Google 并没有反对这么做: https://stackoverflow.com/a/39625963, https://stackoverflow.com/a/2256312) 有一种后端的通信方式可以尝试: app 并不直接与 Resource Server 的 API 交互, 而是通过一个 proxy server. 对于 auth server 和 resource server 来说, 存储着 client id 和 secret 的 proxy server 就是一个授权的 client, app 与 proxy server 的通信也能避免跨域问题, 但是否有潜在问题有待进一步验证.
Fig 2
接下来考虑, 如果只要第三方 app 遵循 OAuth 2.0 标准, 原生应用走常规的登录模型呢?
但是, 如Fig1,2这样做了, 也就是把 API 的访问权限扩大为登录用户了, 因为通过上述方式访问 API 只要求身份认证即可.
为了进一步寻求答案, 我们先后调研了 Google, Microsoft, Facebook, Github, Twitter 的用户身份认证及内容授权模型. 其中只有 Twitter 和 Facebook 属于 SPA 应用, 其它或多或少都存在传统的交互.
Case: Twitter
在身份认证上, 存在一个独立的登录 API, 用于网站/ app用户登录. 提交登录信息时并不存在 client id之类的标识信息, 成功后返回一个auth_token. 存在一个专门用于 Authorization 的 API, 用于通过 Twitter 登录第三方应用.
在资源访问上, 存在 website 专属 API 和开发者 REST API, 二者的调用方式不同. 前者只需提交登录后保存在 cookies 中的 auth_token 即可.
那么 Twitter 是如何解决上述问题?
通过 chrome dev-tool 可以发现, 每次登录时向一个接口
/sessions
发起 POST 请求, 携带的 body 除了 username 和 password 之外, 还有一个authenticity_token
, 但是这个 Token 不是 access_token, 而是用作防止 CSRF 的 校验 token, 这个 token 由 server 生成并包含在只有通过加载 HTML 的方式取得:因此, 除非首先访问 Twitter 登录页, 然后提取出 token. 这也为访问这些 website API 增加了障碍.
Conclusion
很遗憾目前没有发现符合最初调研目地的方案, 但已经尽力而为的去做了相关的调查, 或许那种理想中的方案本身并不是一个 best practice.
此外, 以下并不是 reference, 而是更多关于如何设计安全的 API 上可以参考的资料: