iOS App 中 OAuth 授权的实现方式

https://blog.****.net/qijianli/article/details/8025776

如果真要做登陆模块可以参考下:https://blog.****.net/nhwslxf123/article/details/78757875

在 iOS App 中,需要绑定微博、Twitter、Flickr 等第三方平台账号时,一般用 OAuth 授权的方式。OAuth 1.0 授权大致分为以下三步:

  1. 客户端向平台申请一个 request token,该 token 是未授权的;
  2. 客户端打开平台提供的登陆页面,引导用户输入用户名密码,对 request token 进行授权。 登陆页面的 url 中会附带一个 redirect_url,当授权成功后会重定向到这个地址,返回客户端。
  3. 客户端拿着已授权的 request token 向平台换取一个 access token,后续就使用 access token 访问平台提供资源。

OAuth 2.0 授权的流程与 1.0 有所变化,但 2、3 两步还是存在的。 关键就是其中的第二步,用户是在平台而不是客户端提供的页面上输入用户名密码, 例如淘宝绑定新浪微博的页面,域名是新浪的:

iOS App 中 OAuth 授权的实现方式

OAuth 保证了客户端始终接触不到用户名密码,这样就保证了用户账号的安全,OAuth 发明的意义就在这里。 一旦发现客户端做坏事,只需禁用相应的 access token 即可,不用担心自己的密码泄漏。

Web App 下第二步的登陆流程,一般会在打开的新窗口中进行。但在 iOS App 里,授权登陆的实现形式就可以有以下三种形式:

内嵌 WebView

这种方法是在 App 内置的 WebView 里打开登陆页面完成授权,例如新版 Instagram 绑定新浪微博 (下图),Path 绑定 Twitter 等。

iOS App 中 OAuth 授权的实现方式

许多平台提供的 SDK 都实现了这个流程,例如 sinaweiboios 使用一个嵌入 WebView 的 modal ViewController, 而人人网 SDK 则可以选择弹出浮出层或者 push NavigationController 来显示登陆窗口。

不用 SDK 的话自己实现也不难,需要在合适的地方放一个 WebView 打开登陆页面。 关键是要设置这个 WebView 的 delegate, 实现 delegate 的webView:shouldStartLoadWithRequest:navigationType: 方法, 在里面检查目标 URL 是否等于 callback redirect_url,若相等则说明授权成功,关闭 WebView 即可。

这种实现方式存在以下两个明显的缺点:

  1. 由于登陆页面是嵌入在 WebView 里的,用户无法判断这个页面是由平台提供的,还是 App 伪造的,丧失了 OAuth 的最大优势,用户的密码安全得不到保障;
  2. 不同的 App 需要向同一个平台授权时,用户需要反复输入用户名密码。跟第一点相比,这点用户体验的损伤真不算什么。

切换到 Safari 进程

这种方法是切换到 Safari 进程,打开登陆页面,完成授权后再切换到 App 进程中。 例如 Instagram 绑定 Flickr,甚至 Flickr App 登陆到自己的账户都是用这种方式实现的。

iOS App 中 OAuth 授权的实现方式

实现这种方法,能切换成功的关键在于 App 需要注册自己的 URL scheme, 并用一个符合此 scheme 的 URL 作为授权完毕的回调地址, 这样浏览器打开回调地址时就能返回 App。 例如我们的全国空气污染指数的 url scheme 是 dirtybeijing, 授权完成的回调地址就是 dirtybeijing://sns_authorized/weibo。 在 app delegate 中实现application:openURL:sourceApplication:annotation: (iOS ≥ 4.2) 或者application:handleOpenURL: (iOS < 4.2) 即可捕获通过 URL scheme 打开 App 的事件,从而完成 OAuth 授权的后续流程。

相比内嵌 WebView,这种方法的优点:

  1. 登陆页面通过操作系统浏览器打开,用户可以通过检查地址栏中的域名,以及是否使用了 https 来确认登陆页面不是第三方 App 伪造的;
  2. 同一个账号多次授权不同的 App 时,可以共享浏览器 cookie,使得后续的授权不需要再输入用户名密码。

当然,这种方法也有其自身的缺点:

  1. 兼容性: 由于依赖 iOS 多任务切换,所以一般只能用于 4.0 以后的操作系统。 iOS 4.0 之前不支持多任务,所以 App 需要在切换到 Safari 之前自行保存现场, 在从 Safari 返回以后再自行恢复现场,实现比较复杂。
  2. 多任务切换,会给用户一种流程中断的感觉。
  3. 会在 Safari 中留下一个未关闭的 tab。

腾讯微博的授权页面上,就指出了必须用这种方法完成授权,禁止使用内嵌的方法。 不过大多数开发者貌似没怎么严格执行,腾讯的审核也不严格。

iOS App 中 OAuth 授权的实现方式

切换到官方应用进程

跟上一种方法类似,只不过将 Safari 替换成了平台的官方应用,最典型的就是 Facebook。 由于用户已经在平台的官方 App 中登陆过,所以授权过程不需要再输入用户名密码,也不会在 Safari 中留下未关闭的 tab, 在保证安全性的前提下体验是最好的。 但限制也很明显:需要官方应用支持、需要用户已安装过官方应用。

Facebook SDK 中的授权过程,首先尝试使用官方 App,若未安装 Facebook App 或者操作系统不支持多任务, 则会打开 Safari 完成授权;若 Safari 仍然打开失败,则会在嵌入的 WebView 打开登陆页面。 完整实现可参考 Facebook.m 中的authorizeWithFBAppAuth:safariAuth: 方法。

总之,建议 App 开发者需要实现 OAuth 授权时,为保证安全性,尽量使用切换到 Safari 或者官方应用的方式进行。 用户在使用 App 时,若第三方账号的登陆界面是 app 内嵌的,一定要小心。 类似下图这种绑定形式太吓人了,直接向一个 App 透露微博的用户名密码,用着实在不放心。

对登录逻辑做一些总结。 
在APP中登录有两种: 
APP内部登录,以及第三方登录。 
一般APP内部登录,采用token代替网站的cookies。做法是用户先用账户名/密码取得token,然后做后续访问时携带token,当token过期后再次请求。 

第三方登录,为了提高安全性,一般采用OAuth2.0。即在第三方平台注册app的clientid,设置回调界面,然后在本地APP调用第三方平台相关API。得到授权后将token存放到本地,携带此token访问第三方平台相关资源。过期则重新请求~具体流程见下图:

iOS App 中 OAuth 授权的实现方式

OAuth2.0的安全性解读: 
http://drops.wooyun.org/papers/598

OAuth 2 一般不适用公司内部 API 调用,因为它的主要目的是解决资源授权的问题,而且 OAuth 2 里面角色对于 C/S 结构的 app 来说太过于繁杂了,不太有必要折腾。 
移动 app 比较简单的方法还是使用 token(一种类似与 http cookie 的东西),登录之后得到 token,任何请求都必须带上它。因为是内部账户体系,登录也可以直接使用用户名密码,验证成功服务器就返回 token,没有必要做各种 code/token 交换的事情。 
不过如果公司资源变得非常独立和分离了,OAuth 2 还是很有价值的。在我的公司,为了让公司内部统一用户名密码,我就实现了一个基本的 OAuth 2 流程,负责给各种内网网站授权,确实比较方便。

OAuth授权大致分为以下三步: 
客户端向平台申请一个 request token,该 token 是未授权的; 
客户端打开平台提供的登陆页面,引导用户输入用户名密码,对 request token 进行授权。 登陆页面的 url 中会附带一个 redirect_url,当授权成功后会重定向到这个地址,返回客户端。 
客户端拿着已授权的 request token 向平台换取一个 access token,后续就使用 access token 访问平台提供资源。 
第二部又可以有三种方法:webview,safari,第三方app,参考http://blog.****.net/qijianli/article/details/8025776