时间:2016.12.26
背景:在 iOS 开发中,大部分应用的用户登录机制都是基于 token 令牌的,我之前做过的项目也都是如此,但是在我现在的新项目中,由于服务端同学是沿用他们以前的 cookie 登录机制,所以我也不得不换种方式来对(zhe)待(teng)登(yi)录(xia)问题了。
1.什么是 cookie?cookie 和 token 有何区别?
cookie 是什么呢?cookie 在英语中通常是指饼干,当然,我这里指的不是,而是 HTTP 网络请求中用来记录用户信息的一种数据形式或者说一种机制。
cookie:在客户端发送登录操作的网络请求时,服务器在登陆成功返回的 response header 中会添加一个 set-cookie 的值,作为用户的身份认证,如果是浏览器的话,后面每一次发请求时,浏览器都会自动将之前获取到的 cookie 值插入到 request header 的 cookie 字段中,而且 cookie 本身包括多个属性,比如有效期 expires、域 domain等,因此采用 cookie 的登录机制需要考虑到对 cookie 本身的管理。cookie 主要是在 web 领域使用。
token:相比 cookie,token 令牌的登录机制要更轻,直观的感受是,登录认证成功后,服务器返回 token 值,然后在请求的 url 中拼接一段 “token=%^&%#&%#&” 就完事了,至于什么跨域、安全策略什么的,根本没他什么事,客户端管理 token 也非常简单,只要看好这个字符串就行了,所以 token 一般在移动端用的比较多。当然,移动应用中的 web view 还是要处理 cookie 的。
2.iOS 中的网络请求中如何处理 cookie?
在开始处理 cookie 时,需要了解两个类,NSHTTPCookie 和 NSHTTPCookieStorage,在用的时候要注意几点:
- 在请求时,NSURLSession 和 NSURLConnection 会自动帮我们管理 cookie 的,但并不完善。AFNetworking 默认设置了 NSURLRequest 的 HTTPShouldHandleCookies 属性为 YES。
- 如果服务器设置了 Cookie 失效时间 expiresDate,并且 sessionOnly 为 FALSE,Cookie 就会被持久化到文件中,下次启动app会自动加载沙盒中的 Cookies。如果 sessionOnly 为 TRUE 或者 expiresDate 为空,则不会自动持久化到沙盒。
- 手动设置的 Cookie 不会被 NSHTTPCookieStorage 自动持久化到沙盒。
- 不能简单地依赖 NSHTTPCookieStorage 的 setCookie: 方法来做 cookie 的存储,因为在执行 setCookie:时, cookie 并不是马上就更新了。参考: NSHTTPCookieStorage state not saved on app exit. Any definitive knowledge/documentation out there?
- cookie 的 httpOnly 属性是用来设置 cookie 是否能通过 js 去访问。默认情况下,cookie不会带httpOnly选项(即为空),所以默认情况下,客户端是可以通过 js 代码去访问(包括读取、修改、删除等)这个cookie的。当cookie带 httpOnly 选项时,客户端则无法通过js代码去访问(包括读取、修改、删除等)这个cookie。参考:聊一聊 cookie。
下面切入正题吧,我是如何做的呢?
首先是登录。登录成功后,服务器在 HTTP response header 中的 set-cookie 字段中返回了 cookie 的值,我们可已通过多种方式获取到我们想要的 cookie 值,我是采用了下面这种方式来读取的,因为我们的服务器没有设置 expireDate,所以我就自己做持久化存储了。
NSDictionary *headerFields = [(NSHTTPURLResponse *)response allHeaderFields];
NSArray <NSHTTPCookie *> *cookies =[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies; // 只要服务器在请求返回时带了 cookie,NSHTTPCookieStorage 就会自动帮我们管理 cookie
DDLogDebug(@"\n shared cookies %@\n", cookies);
for (NSHTTPCookie *aCookie in cookies) {
if ([aCookie.name isEqualToString:@"BUA"]) { // 获取并保存用户cookie
[[NSUserDefaults standardUserDefaults] setObject:cookie.properties forKey:kYIDUserDefaultUserCookieKey]; // 自己做持久化存储
break;
}
}
然后是请求时添加 cookie 到 request header。实际上这一步系统(NSURLSession / NSURLConnection)已经自动帮我们处理了,具体细节我也不太清楚。
还要考虑重启应用后的操作,由于我们的服务器没有设置 expireDate 以及上面提到的其他原因,在程序重启时,NSHTTPCookieStorage 并不会保存上一次使用应用时的 cookie,所以我们需要在程序启动时读取自己保存的 cookie,同时更新 NSHTTPCookieStorage 的 cookie。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { {
NSDictionary *cookieProperties = [[NSUserDefaults standardUserDefaults] objectForKey:kYIDUserDefaultUserCookieKey];
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
...
}
关于 cookie 的有效期处理,在使用 cookie 时需要自己判断 cookie 是否过期,NSHTTPCookieStorage 是不会自动帮我们处理的,更何况我们自己还做了本地存储,所以我们在用到 cookie 时还需要检查 cookie 是否过期,如果过期了,就要废弃掉失效的 cookie。我是在用户的登录状态方法中做的处理:
- (BOOL)isLoggedIn {
if (![self.cookie yid_isNotExpired]) { // cookie 失效,自动退出登录
[self logout]; // 删除用户信息、cookie
}
return [self.cookie yid_isNotEmpty];
}
最后还要记得在退出登录时也要删除 cookie:
[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:[self userCookie]];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kYIDUserDefaultUserCookieKey];
3.iOS 中的原生网络请求如何与 webView 实现 cookie 共享?
UIWebView 是属于 URL Loading System 的一部分,所以系统会自动帮我们将 NSHTTPCookieStorage 中的 cookie 同步到 UIWebView 中去。
由于 WKWebView 是独立于 URL Loading System 之外的,所以 WKWebView 所有的 cookie 管理都需要开发者自己操作,具体方法可以参考 stackoverflow 上的解决方案:Can I set the cookies to be used by a WKWebView?,也有国内开发者根据这个答案造了一个轮子 haifengkao/YWebView。
遗留问题:
1.服务器是在什么时候更新/生成cookie ?
2.登陆成功后,系统是如何自动添加 cookie 到 request header 中去的?
3.服务器是怎么识别客户端的 cookie 的?
参考资料:
- Wikipedia - HTTP cookie:
https://en.wikipedia.org/wiki/HTTP_cookie- 聊一聊 cookie:
https://segmentfault.com/a/1190000004556040- NSHTTPCookieStorage 官方文档:
https://developer.apple.com/reference/foundation/nshttpcookiestorage
中文版:http://www.jianshu.com/p/b10652a1803e- iOS平台下cookie的使用:
http://www.jianshu.com/p/65094611980c- iOS HTTP网络请求Cookie的读取与写入(NSHTTPCookieStorage)
http://www.skyfox.org/ios-url-request-cookie.html
- NSHTTPCookieStorage state not saved on app exit. Any definitive knowledge/documentation out there?
http://stackoverflow.com/questions/5837702/nshttpcookiestorage-state-not-saved-on-app-exit-any-definitive-knowledge-docume - app开发token、cookie的区别,账号密码加密又是如何保证安全?
https://www.zhihu.com/question/39137156 - 为什么 APP 要用 token 而不用 session 认证?
https://www.v2ex.com/t/148426 - How to manage sessions with AFNetworking?
http://stackoverflow.com/questions/10984374/how-to-manage-sessions-with-afnetworking/11039784#11039784 - Persisting Cookies In An iOS Application?
http://stackoverflow.com/questions/4597763/persisting-cookies-in-an-ios-application - NSHTTPCookieStorage and Cookie Expiration Date
http://stackoverflow.com/questions/7203641/nshttpcookiestorage-and-cookie-expiration-date/7209706#7209706 - Can I set the cookies to be used by a WKWebView?http://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview
- haifengkao/YWebView:https://github.com/haifengkao/YWebView