前言
在Hybrid項目不同容器間共享Cookie在UIWebview時代一直不是一個問題,但是在很多Hybrid框架迭代到WKWebview后就可以出現關于Cookie共享的一些問題,這些問題無外乎就兩點:
- 原生請求(NSURLSessionDataTask、AFNetworking等和)WKWebview之間cookie的共享
- 不同WKWebview (包括webview內的ajax請求) 之間Cookie的共享
解決以上兩個問題即可解決整個應用內絕大部分情況的Cookie共享問題。
產生這個問題的原因是什么?
關于Cookie的原理我不再做過多的解釋,但是這是這篇文章的前提,必須在了解清楚之后才能合理控制其共享機制。
對于干iOS開發的小伙伴們大家可能對NSHTTPCookieStorage比較熟悉,NSHTTPCookieStorage是一個單例,用來管理整個項目的Cookie,包括UIWebview的Cookie也是由其管理,因此在使用UIWebview的情況下是沒有任何問題的。但是WKWebview的Cookie信息并不存儲在NSHTTPCookieStorage中,其由WKProcessPool管理。
多WKWebview間Cookie的共享
先來看WKWebview之間Cookie的共享問題如何解決
方法一
默認情況下,每一個WKWebview對象持有一個WKProcessPool對象,因此可以通過單例化WKProcessPool的方式解決WKWebview間Cookie共享的問題,但是存在的問題是WKProcessPool不會被持久化,應用被殺死后會導致Cookie丟失,對于需要長久保存的Cookie并不合適。方法二
使用NSHTTPCookieStorage對象去手動存取Cookie信息并注入到WKWebview中
先來說怎么取,直接上代碼:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
if (@available(iOS 12.0, *)) {
WKHTTPCookieStore *cookieStore = webView.configuration.websiteDataStore.httpCookieStore;
[cookieStore getAllCookies:^(NSArray* cookies) {
[self setCookie:cookies];
}];
}else {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
[self setCookie:cookies];
}
decisionHandler(WKNavigationResponsePolicyAllow);
}
- (void)setCookie:(NSArray *)cookies {
for (NSHTTPCookie *cookie in cookies) {
NSHTTPCookie *httpCookie = [self fixExpiresDateWithCookie:cookie];
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:httpCookie];
}
}
- (NSHTTPCookie *)fixExpiresDateWithCookie:(NSHTTPCookie *)cookie{
NSMutableDictionary *propertiesDic = [[cookie properties] mutableCopy];
if (![propertiesDic valueForKey:@"expiresDate"]) {
propertiesDic[NSHTTPCookieExpires] = [NSDate dateWithTimeIntervalSinceNow:60*60*24*7];
propertiesDic[NSHTTPCookieDiscard] = 0;
}
NSHTTPCookie *newCookie = [NSHTTPCookie cookieWithProperties:propertiesDic];
return newCookie;
}
代碼比較簡單,唯一需要注意的就是fixExpiresDateWithCookie這個方法的實現中修改了Cookie的過期時間NSHTTPCookieExpires與是否丟棄NSHTTPCookieDiscard字段,修改原因是因為后端不愿意改代碼,喝喝,當然也可以理解,B/S架構中session-only=1這樣的設置較為安全,也是常態化操作,但是對于客戶端多個webview對象就很尷尬了。同時這個操作的確可能帶了信息泄露的安全問題,安全風險一定需要注意。
接下來說怎么注入到WKWebview中去,有兩種思路
第一種是通過WKUserScript去注入,但是注入時機會影響服務器可能拿不到Cookie。
第二種方法是在構造NSURLRequest的時候去修改請求頭,也直接上代碼了:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
NSMutableString *cookiesString = [NSMutableString string];
NSArray *tmpCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:url];
for (NSHTTPCookie * cookie in tmpCookies) {
NSString *cookieString = [NSString stringWithFormat:@"%@=%@;",cookie.name,cookie.value];
[cookiesString appendString:cookieString];
}
[request setValue:cookiesString forHTTPHeaderField:@"Cookie"];
[self loadRequest:request];
原生請求與WKWebview之間Cookie的共享
獲取和保存Cookie的方式與上面類似,再處理網絡請求response的地方調用如下類似代碼:
- (void)syncSwordCookies:(NSURLResponse *)response forURL:(NSURL *)url {
NSDictionary *respHeader = [(NSHTTPURLResponse *)response allHeaderFields];
NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:respHeader forURL:url];
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in cookies) {
[cookieStorage setCookie:[self fixExpiresDateWithCookie:cookie]];
}
}
Cookie都保存在NSHTTPCookieStorage中后,原生請求自動共享Cookie,webview注入方式即與前面說的WKWebview一樣的,就不用贅述了,但是唯一要注意的地方就是NSHTTPCookieStorage保存有一個耗時時間,大概零點幾秒,不能在保存后立刻去同步到WKWebview中去,否則容易取不到Cookie(真坑...)
結語
WKWebview Cookie同步的坑我在全部踏完之后留下來上述結論,還有些低級或者麻煩的坑就沒有一一列舉,希望這篇文章可以給到大家幫助,如果有什么疑問可以留言討論。