OAuth 授权在 iOS 中的实现方式
在 iOS App 中,需要绑定微博、twitter、flickr 等第三方平台账号时,一般用OAuth 授权的方式。 OAuth 1.0 授权大致分为以下三步:
- 客户端向平台申请一个 request token,该 token 是未授权的;
- 客户端打开平台提供的登陆页面,引导用户输入用户名密码,对 request token 进行授权。 登陆页面的 url 中会附带一个 redirect_url,当授权成功后会重定向到这个地址,返回客户端。
- 客户端拿着已授权的 request token 向平台换取一个 access token,后续就使用 access token 访问平台提供资源。
OAuth 2.0 授权的流程与 1.0 有所变化,但 2、3 两步还是存在的。 关键就是其中的第二步,用户是在平台而不是客户端提供的页面上输入用户名密码, 例如淘宝绑定新浪微博的授权登陆页面,域名是新浪的:
OAuth 保证了客户端始终接触不到用户名密码,这样就保证了用户账号的安全,OAuth 发明的意义就在这里。 一旦发现客户端做坏事,只需禁用相应的 access token 即可,不用担心自己的密码泄漏。
Web App 下第二步的登陆流程,一般会在打开的新窗口中进行。但在 iOS App 里,授权登陆的实现形式就可以有以下三种形式:
内嵌 WebView
这种方法是在 app 内置的 WebView 里打开登陆页面完成授权,例如新版 Instagram 绑定新浪微博 (下图),Path 绑定 twitter 等。
许多平台提供的 sdk 都实现了这个流程,例如 sinaweiboios 使用一个嵌入 WebView 的 modal ViewController, 而人人网 sdk 则可以选择弹出浮出层或者 push NavigationController 来显示登陆窗口。
不用 sdk 的话自己实现也不难,需要在合适的地方放一个 WebView 打开登陆页面。 关键是要设置这个 WebView 的 delegate, 实现 delegate 的webView:shouldStartLoadWithRequest:navigationType:
方法, 在里面检查目标 URL 是否等于 callback redirect_url,若相等则说明授权成功,关闭 WebView 即可。
这种实现方式存在以下两个明显的缺点:
- 由于登陆页面是嵌入在 WebView 里的,用户无法判断这个页面是由平台提供的,还是 app 伪造的,丧失了 OAuth 的最大优势,用户的密码安全得不到保障;
- 不同的 app 需要向同一个平台授权时,用户需要反复输入用户名密码。跟第一点相比,这点用户体验的损伤真不算什么。
切换到 Safari 进程
这种方法是切换到 Safari 进程,打开登陆页面,完成授权后再切换到 app 进程中。 例如 Instagram 绑定 Flickr,甚至 Flickr app 登陆到自己的账户都是用这种方式实现的。
实现这种方法,能切换成功的关键在于 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,这种方法的优点:
- 登陆页面通过操作系统浏览器打开,用户可以通过检查地址栏中的域名,以及是否使用了 https 来确认登陆页面不是第三方 app 伪造的;
- 同一个账号多次授权不同的 app 时,可以共享浏览器 cookie,使得后续的授权不需要再输入用户名密码。
当然,这种方法也有其自身的缺点:
- 兼容性: 由于依赖 iOS 多任务切换,所以一般只能用于 4.0 以后的操作系统。 iOS 4.0 之前不支持多任务,所以 app 需要在切换到 Safari 之前自行保存现场, 在从 Safari 返回以后再自行恢复现场,实现比较复杂。
- 多任务切换,会给用户一种流程中断的感觉。
- 会在 Safari 中留下一个未关闭的 tab。
腾讯微博的授权页面上,就指出了必须用这种方法完成授权,禁止使用内嵌的方法。 不过大多数开发者貌似没怎么严格执行,腾讯的审核也不严格。
切换到官方应用进程
跟上一种方法类似,只不过将 Safari 替换成了平台的官方应用,最典型的就是 Facebook。 由于用户一般已经在平台的官方 app 中登陆过,所以授权过程不需要再输入用户名密码,也不会在 Safari 中留下未关闭的 tab, 在保证安全性的前提下体验是最好的。 但限制也很明显:需要官方应用支持、需要用户已安装过官方应用。
Facebook SDK 中的授权过程,首先尝试使用官方 app,若未安装 Facebook app 或者操作系统不支持多任务, 则会打开 Safari 完成授权;若 Safari 仍然打开失败,则会在嵌入的 WebView 打开登陆页面。 完整实现可参考 Facebook.m 中的authorizeWithFBAppAuth:safariAuth:
方法。
总之,建议 app 开发者需要实现 OAuth 授权时,为保证安全性,尽量使用切换到 Safari 或者官方应用的方式进行。 用户在使用 app 时,若第三方账号的登陆界面是 app 内嵌的,一定要小心。 类似下图这种绑定形式太吓人了,直接向一个 app 透露微博的用户名密码,谁敢用啊。