1. 程式人生 > >多個WKWebView頁面的cookie不共享問題及解決方案

多個WKWebView頁面的cookie不共享問題及解決方案

本人在開發過程中遇到一個奇怪的問題,採用UIWebView時,用微信授權後進入繫結手機號頁面,繫結手機號成功,然後重新生成一個頁面(UIViewController主頁),進入新頁面銷燬繫結手機號h5頁面(UIViewController),主頁正常顯示。但是採用WKWebView,同樣的處理,這個主頁顯示是沒有繫結手機號的下載二維碼頁面。網上搜索到的說WKWebView的cookie需要使用者注入,而UIWebView是cookie自己注入和儲存。我把cookie從繫結手機號頁面取出,傳遞到主頁頁面並且注入這個cookie還是不能顯示主頁。分析是cookie的問題,不知道獲取和注入的cookie哪裡出問題了,希望大神指點?沒有辦法,現在暫時不跳轉頁面,一個控制器處理所有js頁面的顯示了。
原因是: WKWebView 是一個多程序元件,每個WKWebView頁面程序都有自己的cookie,它們向伺服器傳送請求時都自己帶上自己的cookie,所以你在app中無論怎麼攔截都發現請求中沒有帶cookie,實際上WKWebView頁面程序肯定代了帶了cookie,不然伺服器返回錯誤。列印的cookie是:Cookie:JSESSIONID=A2B33F508E609B8208D8EA148114794E; _bl_uid=sOjwaley6yX6OFcn3nap0qt6p8dR。並且我測試發現_bl_uid有低概率沒有,JSESSIONID都存在,還存在兩個相同的_bl_uid帶不同的值的情況。估計這就是WKWebView的cookie返回的說法吧,至於兩對bl_uid鍵值對,估計是強制想向請求的HTTPHeaderFields注入cookie引起。而一旦註冊 http(s) scheme 後,你發現你跳轉的新頁面就正常,並且cookie的鍵值對還多了一對(如:Cookie:JSESSIONID=143219E3B0D66946C4D949D50811F88C; _bl_uid=e9jqhlz96tX9Fhhv9fh94s2qtRvm; PS=o8SkWwD1RV7VYdPH_PGzgC5EYdv4。注意:這裡指的是通過[NSHTTPCookieStorage sharedHTTPCookieStorage]獲取到的app本地cookie, 不是通過通常decidePolicyForNavigationResponse(實際上兩種情況通過該函式獲取的cookie只有JSESSIONID=143219E3B0D66946C4D949D50811F88C一對鍵值,只有讀本地cookie時不同)獲取到的cookie。),fsCachedData存在大量快取資料(註冊 http(s) scheme前的app沒有那麼多資料),但是這樣做的嚴重後果是post 請求 body 資料被清空(這個問題我遇到過,是現在一直真實存在的問題)。若從正常的微信授權成功h5頁面A跳轉的新控制器頁面(h5頁面)B,若在B頁面載入傳送請求時設定cookie,那麼B頁面載入失敗,從B頁面返回A頁面,重新整理A頁面的相同控制器的子頁面請求全部失敗。我研究了三天了,想在不註冊http(s) scheme 的情況下正常載入B頁面成功都不可能。
WKWebView中Cookie混亂問題:按道理來說每個WKWebView都有一個單獨的儲存Cookies的空間,相互不影響,但是,奇妙之處就是我在一個UIViewController中生成了一個WKWebView,然後進行了一系列的網路訪問後,推出並銷燬這個UIViewcontroller;在下次進來的時候這個WKWebView會攜帶上次訪問的部分Cookies。
  這個原因是WKWebView會將Cookie儲存到沙盒目錄的檔案中,下次WKWebView被例項化的時候,會去同步這個檔案中的Cookies。
  decidePolicyForNavigationAction函式中navigationAction.request是隻讀的,decidePolicyForNavigationResponse函式的navigationResponse.response也是隻讀的。你在這些函式中也沒有辦法重置請求的allHTTPHeaderFields的欄位。
使用UIWebView沒有這樣的問題,這也許是UIWebView沒有完全代替WKWebView原因之一吧!
參考文章:《【騰訊Bugly乾貨分享】WKWebView 那些坑》

https://blog.csdn.net/tencent_bugly/article/details/54668721/
測試使用的程式碼如一,它實際上及時更新cookie檔案,由於WKWebView和本app不在一個程序中,它們不在一個程式空間,他們都有自己的cookie,它們兩者之間資源共享需要程序間通訊,你及時更新的是app空間的cookie,不能處理WKWebView的cookie及時同步到app空間,所以不能解決該問題:

//這個是網頁載入完成,導航的變化
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    NSString
*strRequest = [webView.URL.absoluteString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; FLDDLogVerbose(@"isHaveTelLoginPage:%d, webView.URL strRequest:%@",[AWSingleObject sharedInstance].isHaveTelLoginPage, strRequest); // 獲取載入網頁的標題 self
.titleLabel.text = self.wkWebView.title; //取出cookie NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; //js函式 NSString *JSFuncString = @"function setCookie(name,value,expires)\ {\ var oDate=new Date();\ oDate.setDate(oDate.getDate()+expires);\ document.cookie=name+'='+value+';expires='+oDate+';path=/'\ }\ function getCookie(name)\ {\ var arr = document.cookie.match(new RegExp('(^| )'+name+'=({FNXX==XXFN}*)(;|$)'));\ if(arr != null) return unescape(arr[2]); return null;\ }\ function delCookie(name)\ {\ var exp = new Date();\ exp.setTime(exp.getTime() - 1);\ var cval=getCookie(name);\ if(cval!=null) document.cookie= name + '='+cval+';expires='+exp.toGMTString();\ }"; //拼湊js字串 NSMutableString *JSCookieString = JSFuncString.mutableCopy; for (NSHTTPCookie *cookie in cookieStorage.cookies) { NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", cookie.name, cookie.value]; [JSCookieString appendString:excuteJSString]; } //執行js [webView evaluateJavaScript:JSCookieString completionHandler:^(id obj, NSError * _Nullable error) { NSLog(@"%@",error); }]; } - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{ NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response; NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL]; for (NSHTTPCookie *cookie in cookies) { [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; NSLog(@"cookie:%@", cookie); } decisionHandler(WKNavigationResponsePolicyAllow); }

測試方法二,B頁面加載出來的不是期望的cookie校驗成功的頁面, 由於WKWebView和本app不在一個程序中,它們不在一個程式空間,他們都有自己的cookie,它們兩者之間資源共享需要程序間通訊,你及時更新的是app空間的cookie,不能處理WKWebView的cookie及時同步到app空間,所以不能解決本問題:

#pragma mark - 頁面載入前處理
- (void)beforePush:(NSDictionary *)params
{

    [super beforePush:params];
    NSDictionary *userInfo = params[MGJRouterParameterUserInfo];
    if ([[userInfo safeObjectForKey:@"jsWebEntity"] isKindOfClass:[AWJsWebEntity class]]) {
        AWJsWebEntity *jsWebEntity = [userInfo safeObjectForKey:@"jsWebEntity"];
        NSMutableArray *cookiesArr = [NSMutableArray array];
        /** 獲取NSHTTPCookieStorage cookies */
        NSHTTPCookieStorage * shareCookie = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        for (NSHTTPCookie *cookie in shareCookie.cookies){
            [cookiesArr addObject:cookie];
        }
        self.cookiesArr = cookiesArr;

//        self.cookies= [userInfo safeObjectForKey:@"cookies"];
        [self loadWebURLSring:jsWebEntity.url];
    }
}

#pragma mark ================ 載入方式 ================

- (void)webViewloadURLType{
//    NSMutableURLRequest * Request_zsj = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.URLString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    NSMutableURLRequest *Request_zsj = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.URLString]];
    NSString *cookie = [self readCurrentCookieWithDomain:self.URLString];
    //cookie = [NSString stringWithFormat:@"%@;PS=o8SkWwD1RV7VYdPH_PGzgC5EYdv4", cookie];
    [Request_zsj addValue:cookie forHTTPHeaderField:@"Cookie"];

    NSString *cookieSting = @"";
    for (NSHTTPCookie *cookie in self.cookiesArr){
        if(!isEmptyString(cookieSting))
        {
            cookieSting = [NSString stringWithFormat:@"%@; %@=%@",cookieSting, cookie.name,cookie.value];
        }
        else
        {
            cookieSting = [NSString stringWithFormat:@"%@=%@",cookie.name,cookie.value];
        }
    }
//    [Request_zsj setValue:cookieSting forHTTPHeaderField:@"Cookie"];
    NSLog(@"Cookie:%@", cookieSting);
    [Request_zsj setValue:cookieSting forHTTPHeaderField:@"Cookie"];
//    [Request_zsj setValue:@"Mozilla/5.0 (iPhone; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15G77Yixiangweipai/0.0.1" forHTTPHeaderField:@"User-Agent"];
    NSLog(@"task.url:%@ \n   currentRequest.allHTTPHeaderFields:%@", [NSString stringWithFormat:@"%@", Request_zsj.URL], Request_zsj.allHTTPHeaderFields);
//    WKHTTPCookieStore *cookieStore = self.wkWebView.configuration.websiteDataStore.httpCookieStore;
//    [cookieStore setCookie:self.cookies completionHandler:nil];
    //載入網頁
    [self.wkWebView loadRequest:Request_zsj];
}

- (NSString *)readCurrentCookieWithDomain:(NSString *)domainStr{
    NSHTTPCookieStorage*cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    NSMutableString * cookieString = [[NSMutableString alloc]init];
    for (NSHTTPCookie*cookie in [cookieJar cookies]) {
        [cookieString appendFormat:@"%@=%@;",cookie.name,cookie.value];
    }

    //刪除最後一個“;”
    [cookieString deleteCharactersInRange:NSMakeRange(cookieString.length - 1, 1)];
    return cookieString;
}

測試方法三:在A頁面註冊http(s) scheme 的情況[NSURLProtocol wk_registerScheme:@”https”]; 當然你使用的h5頁面地址是以http:開頭的修改為:[NSURLProtocol wk_registerScheme:@”http”];具體SURLProtocol怎麼用,大家在網上搜索一下吧,後期我會寫一篇關於js標籤圖片在iOS替換的文章有相關的介紹)。app會攔截該應用向伺服器傳送的所有https網路請求(包括js,cs,png等資源請求,注意:攔截的請求是WKWebView在HTTPHeaderField加入cookie前的請求,所以在canInitWithRequest函式列印NSLog(@”canInitWithRequest request.URL.absoluteString = %@,請求方式 == %@,scheme:%@,request.allHTTPHeaderFields:%@”,urlStr,request.HTTPMethod,scheme, request.allHTTPHeaderFields);得到是null,如:2018-08-23 14:16:51.390805+0800 ArtEnjoymentWeChatAuction[12030:1142813] canInitWithRequest request.URL.absoluteString = https://m.1-joy.com/market/product/cat/list.htm,請求方式 == GET,scheme:https,request.allHTTPHeaderFields:(null)),並且在fsCachedData快取絕大部份網頁資料(如何獲取fsCachedData資料夾下的檔案,見文章《如何在不越獄的情況下,獲取app中的所有常用檔案和資料夾》https://blog.csdn.net/jia12216/article/details/81536960)。當不註冊http(s) scheme 的情況,fsCachedData資料夾下一般有很少的檔案,幾乎沒有網頁資料。這樣做的嚴重後果是post 請求 body 資料被清空(這個問題我遇到過,是現在一直真實存在的問題),就是h5頁面自己向後臺傳送的帶引數的post請求,後臺收到的請求引數全部沒有。例如在h5頁面上建立聯絡地址,填寫成功以post的方式上傳後臺,那麼引數全部掉丟失。當然h5頁面呼叫iOS原生方法,由iOS使用afnet等控制元件傳送請求傳送給後臺,引數不會丟失,也就是隻有h5頁面(WKWebView直接管理)直接向後臺傳送post請求才會引數丟失。這種方式能解決頁面間的跳轉,但是有問題。
現在我只找到這麼多的方法,只有第三種不完美的方法能解決該問題。網上說的WKWebView的cookie解決方案也就上面兩個類似的方案。大家抄來抄去,根本就沒有實際測試過,解決不了我們的問題。為何需要從A頁面(h5頁面,單獨的UIViewController)跳轉到B頁面(h5頁面,單獨的UIViewController),因為這樣可以把兩者的邏輯分離,若不分離,A頁面和B頁面的邏輯混在一起,就會造成邏輯過於複雜,不利於元件化。
UIWebView是和app在一個程序裡,它們的資料是共享,操作app的cookie和請求就是操作UIWebView的cookie和請求。UIWebView在傳送請求時都自帶cookie。而WKWebView和app不在一個程序中,操作app的cookie和請求並不都能影響操作WKWebView的cookie和請求。有人說使用WKWebView的app,app的cookie有延遲,這個是客觀存在的,因為它們在不同的程序中,程序中的資源是不共享的,它們不是實時同步的,是有同步時機的。當然你想它們實時同步也可以就是註冊http(s) scheme。
有人說WKWebView傳送請求時是不自帶cookie的,這種方法是不正確的,它在WKWebView自己的程序中傳送請求是自帶cookie,只是它怎麼自帶cookie傳送請求蘋果系統沒有向用戶開放,你不知道它怎麼傳送的,它只提供了提供一個NSMutableURLRequest請求給WKWebView。至於你想在這個請求中自己去app的cookie給他,若你沒有跳轉到其他控制器,那麼請求仍舊成功,只是出現本地的cookie可能出現同鍵不同值的鍵值對,若你跳轉了控制器,那麼你新增的cookie無效,因為你不知道WKWebView怎麼自帶cookie的。若真的WKWebView的請求不自帶cookie ,那麼我們微信授權成功進入首頁,讓後在首頁裡跳轉頁面不會都正常的(我們的頁面除了協議頁面基本都校驗cookie的)。我推測可能是你新起一個WKWebView頁面,也就是新起了一個WKWebView程序,這個程序首先找自己的cookie檔案,若沒有直接把請求的cookie設定為空了,當然它剛建立的程序,當然它的cookie檔案不存在了,所以肯定被設定為空了,因此新的WKWebView頁面傳送出去的請求就不自帶cookie了。可見由於WKWebView是多程序元件,cookie也真夠混亂的。
既然頁面不能完美解決兩個h5頁面控制器之間的cookie問題,但是咱們的普通https請求卻不受影響,下面是在h5頁面控制器裡向後臺伺服器傳送https請求的程式碼片段:

    NSURL * url = [NSURL URLWithString:@"https://m.1-joy.com/market/user/weixin/subscribe.htm"];
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
    NSURLSession * session = [NSURLSession sharedSession];
    NSString *cookie = [self readCurrentCookieWithDomain:self.URLString];
    //        cookie = [NSString stringWithFormat:@"%@;PS=o8SkWwD1RV7VYdPH_PGzgC5EYdv4", cookie];
    [request addValue:cookie forHTTPHeaderField:@"Cookie"];
    // 傳送請求
    NSURLSessionTask * sessionTask = [session dataTaskWithRequest:request
                                                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                    if (error) {
                                                        return;
                                                    }
                                                    NSString *mmmmmmm = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                                                    NSLog(@"mmmmmmm: %@, response:%@, error:%@, request.allHTTPHeaderFields:%@", mmmmmmm, response, error, request.allHTTPHeaderFields);
                                                }];
    [sessionTask resume];

做程式設計師就是生死已看淡,不服咱就幹。只有經過實踐的才是真正正確的。不過有的成功可能是有前提條件,並不代表所有的情況。有的失敗也可能是做法有問題,也可能特定情況不符合該解決方案。大家來回抄來抄去,理論上可行,實際上相差十萬八千里。治學要實事求是,不要想當然。