多个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];

做程序员就是生死已看淡,不服咱就干。只有经过实践的才是真正正确的。不过有的成功可能是有前提条件,并不代表所有的情况。有的失败也可能是做法有问题,也可能特定情况不符合该解决方案。大家来回抄来抄去,理论上可行,实际上相差十万八千里。治学要实事求是,不要想当然。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值