時間: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.lxweimin.com/p/b10652a1803e- iOS平臺下cookie的使用:
http://www.lxweimin.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