美文网首页iOS开发iOS用到的IOS知识积累
【iOS】WKWebView使用Cookies遇到的坑

【iOS】WKWebView使用Cookies遇到的坑

作者: 狍子君 | 来源:发表于2017-02-16 21:20 被阅读10943次

    Apple推出WKWebView已经有一段时间了,相对于UIWebView而言,内存占用只有UIWebView的一半左右,但是响应速度和效率上却是UIWebView的两倍。
      总结WKWebView使用方法的帖子文章很多,这里不再赘述,这里重点总结一下cookies共享问题。

    WKWebView会忽视默认的网络存储, NSURLCache, NSHTTPCookieStorage, NSCredentialStorage。 目前是这样的,WKWebView有自己的进程,同样也有自己的存储空间用来存储cookie和cache, 其他的网络类如NSURLConnection是无法访问到的。 同时WKWebView发起的资源请求也是不经过NSURLProtocol的,导致无法自定义请求。

    由于以上原因,导致WKWebView无法与App自身的Cookies、UIWebView之间共享Cookies数据。WKWebView这样做也有一定的好处,在不用操作原有Cookies的基础上,独立的一套Cookies,有效的防止了Web与App Api接口的Cookie相互污染。
      但是在使用过程中也遇到了一些坑。

    有的时候因为业务需求,就是需要相互共享Cookies,这样的案例也有很多,支付宝中大量的Web中,应该就有很多Cookie是和App共享的吧。


    NSHTTPCookieStorage Cookies共享

    在WkWebView接收到Response后,将Response带的Cookies取出,然后直接放入[NSHTTPCookieStorage sharedHTTPCookieStorage] 容器中:

    - (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];
        }
        decisionHandler(WKNavigationResponsePolicyAllow);
    }
    

    然后在完全加载完成后:

    // 页面加载完成之后调用
    - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
        //取出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);
        }];
    }
    

    为什么在完全加载完成后需要重新给WKWebView设置Cookie呢?如果你不这样做的话很有可能因为a标签跳转,导致下一次跳转的时候Cookie丢失。

    302跳转Set-Cookie丢失

    上面的方法可以将服务器Set-Cookie携带到下一次请求中。但是如果302跳转出现在你的第一次加载并且你使用了下面的方法设置第一次加载的Cookies,那么在302跳转时服务器Set-Cookie将不会被携带到下一次302跳转的目标请求中。

    NSMutableURLRequest *request= [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https:/a.com/test1"]];
    [request setValue:[NSString stringWithFormat:@"%@=%@",@"a", @"1"] forHTTPHeaderField:@"Cookie"];
    

    比如:第一次加载https:/a.com/test1,然后加载https:/a.com/test1设置Cookie为a=1,服务器在https:/a.com/test1中Set-Cookie a=2;然后302跳转到https:/a.com/test2,这个时候会发现https:/a.com/test2中获取到的a还是1,Set-Cookie并没有成功。

    解决办法

    1.加载一个本地为空的html,域名指向你的第一次加载的url的域名。

    //加载本地html
    [self.webView loadHTMLString:@"" baseURL:[NSURL URLWithString:@"https:/a.com"]];
    

    2.通过以下方法,在第一次加载完成后,将需要设置的Cookies设置到WKWebView中,因为是加载的本地的html以下方法会立即执行。

    // 页面加载完成之后调用
    - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
        
        if (isFirstLoaded) {
            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=/'\
            }";
            
            //拼凑js字符串,按照自己的需求拼凑Cookie
            NSMutableString *JSCookieString = JSFuncString.mutableCopy;
            for (NSHTTPCookie *cookie in cookieStorage.cookies) {
                if (![cookie.name isEqualToString:@"__cust"]) {
                    NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 3);", cookie.name, cookie.value];
                    [JSCookieString appendString:excuteJSString];
                }
            }
            
            //执行js
            [webView evaluateJavaScript:JSCookieString completionHandler:^(id obj, NSError * _Nullable error) {
                //加载真正的第一次Request
                [self loadRealRequest];
            }];
        }
    }
    

    WKWebView中Cookie混乱

    按道理来说每个WKWebView都有一个单独的存储Cookies的空间,相互不影响,但是,奇妙之处就是我在一个UIViewController中生成了一个WKWebView,然后进行了一系列的网络访问后,推出并销毁这个UIViewcontroller;在下次进来的时候这个WKWebView会携带上次访问的部分Cookies。
      这个原因是WKWebView会将Cookie存储到沙盒目录的文件中,下次WKWebView被实例化的时候,会去同步这个文件中的Cookies,如果不希望它去同步之歌Cookies,那么直接删掉好了。

    - (void)deleteWebCache {
        if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
            NSSet *websiteDataTypes = [NSSet setWithArray:@[
                                    WKWebsiteDataTypeDiskCache,
                                    //WKWebsiteDataTypeOfflineWebApplicationCache,
                                    WKWebsiteDataTypeMemoryCache,
                                    //WKWebsiteDataTypeLocalStorage,
                                    //WKWebsiteDataTypeCookies,
                                    //WKWebsiteDataTypeSessionStorage,
                                    //WKWebsiteDataTypeIndexedDBDatabases,
                                    //WKWebsiteDataTypeWebSQLDatabases
                                    ]];
            //// All kinds of data
            //NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
            //// Date from
            NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
            //// Execute
            [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
                // Done
            }];
        } else {
            NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
            NSString *cookiesFolderPath = [libraryPath stringByAppendingString:@"/Cookies"];
            NSError *errors;
            [[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:&errors];
        }
    
    }
    

    不足之处,欢迎轻轻地喷一下......

    相关文章

      网友评论

      • SDBridge:decidePolicyForNavigationResponse 方法里面获取cookie 的数组 是空,H5页面已经登录了。
      • CathyLiu1213:decidePolicyForNavigationResponse 这个方法中获取cookiesWithResponseHeaderFields根本获取不到cookie的信息,wk的cookie的信息应该是存储在WKWebsiteDataStore的吧
        风尘子:这个问题最后解决了吗?想请教一下
        SDBridge:我也遇到了这个问题
      • andaji:请问,【302跳转Set-Cookie丢失】这部分有demo么?
        我项目中也有这个问题,有几个疑问:
        1. 我什么时候开始执行这个本地请求?[self.webView loadHTMLString:@"" baseURL:[NSURL URLWithString:@"https:/a.com"]];
        是在我要请求302的url之前么?
        2. 但是,我要设置的cookie是302请求返回的SetCookie,在302请求之前我拿不到这个cookie
        3. 如果是在302请求之后,但是,我如何拿到302请求返回的SetCookie呢?
      • 一颗小花菜:您好博主,我在这边关于wkwebview cookie遇到了一个坑,想跟你请教下 ,方便具体聊下么 2833510881 qq
        狍子君:@AnShareJoy 可以在这里描述一下你的问题呢,我也不见得能解答
      • 海东青_9ab9:哥们有demo吗,解决出现302的请求的。
      • jiangyubao001:我使用cordova插件cordova-plugin-wkwebview-engine和cordova-plugin-wkwebview-sync-cookies,解决了首次启动登录的cookie问题,但是这个登录成功的cookie不能长期有效,第二天就失效了,导致又要重新登录,你有好方法么?另外,我试图执行如下代码:

        - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
        NSHTTPURLResponse *response = (NSHTTPURLResponse *) navigationResponse.response;
        if ([response respondsToSelector:@selector(allHeaderFields)] == YES) {
        NSLog(@"支持获取cookie");
        NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
        //读取wkwebview中的cookie 方法1
        for (NSHTTPCookie *cookie in cookies) {
        //[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
        NSLog(@"wkwebview中的cookie:%@", cookie);

        }
        //读取wkwebview中的cookie 方法2 读取Set-Cookie字段
        NSString *cookieString = [[response allHeaderFields] valueForKey:@"Set-Cookie"];
        NSLog(@"wkwebview中的cookie:%@", cookieString);

        //看看存入到了NSHTTPCookieStorage了没有
        NSHTTPCookieStorage *cookieJar2 = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        for (NSHTTPCookie *cookie in cookieJar2.cookies) {
        NSLog(@"NSHTTPCookieStorage中的cookie%@", cookie);
        }
        }
        decisionHandler(WKNavigationResponsePolicyAllow);
        }

        但是发现这个调用[response allHeaderFields]回导致系统崩溃,从崩溃日志来看,response实例没有这个属性allHeaderFields,也不能接受这个消息。这个response的实例并不是NSHTTPURLResponse,请问如何解决这个问题。
      • 95c797dbbaa6:我项目里面都是访问本地的html,用的是loadHTMLString,这样如何设置cookie?
      • 乾坤醉心尘:博主,你这个是清除缓存,而不是cookies?请问清除cookies在iOS8.0系统上怎么操作呢?
      • 追求_卓越:作者您好!请教一下“302跳转Set-Cookie丢失”的问题
        使用您所提供的方法仍然失败,还是会302跳转到H5的登录界面,与您不同的是:
        loadHTMLString的域名为ip类型:@"http://120.3.3.3";
        不知是哪方面的原因?这种情况是否有其他解决办法?
      • 迎风尿三丈:请问如何使用WKWebView有网的时候加载网络,没网的时候显示上一次的缓存?
      • 天涯人1104:还是不行啊,要刷新几次才可以,不懂为何
      • 夏天爱大树:前端做异步请求的时候 有时候cookie会没有带上,做同步请求的时候有cookie,请问遇到过这种问题吗?
      • IMKel:楼主,我项目里一个UIViewController里面有个WKWebView,这个界面有个登录界面,需求要求这个登录之后,只需要保存用户的用户名,UIViewController释放之后,再进入UIViewController界面时,自动填充WKWebView的用户名,这个要怎么实现,我对WKWebView不熟悉,求赐教
        狍子君:@IMKel 我认为可以做js回调app,由app负责保存用户名密码,下次进入的时候把用户名密码写入cookie
        狍子君:@IMKel 不好意思,才看到。
      • 郑嘉成_:请问302重定向 response 里 set-Cookie 没有正确带上,具体怎么解决呢
        文兴:@狍子君 真hack,回头试一下!
        童话镇里蜿蜒的河:@狍子君 如果是多域的,需要切换域,这样就行不通了。 有点局限
        狍子君:解决办法

        1.加载一个本地为空的html,域名指向你的第一次加载的url的域名。

        //加载本地html
        [self.webView loadHTMLString:@"" baseURL:[NSURL URLWithString:@"https:/a.com"]];
        2.通过以下方法,在第一次加载完成后,将需要设置的Cookies设置到WKWebView中,因为是加载的本地的html以下方法会立即执行。

        // 页面加载完成之后调用
        - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{

        if (isFirstLoaded) {
        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=/'\
        }";

        //拼凑js字符串,按照自己的需求拼凑Cookie
        NSMutableString *JSCookieString = JSFuncString.mutableCopy;
        for (NSHTTPCookie *cookie in cookieStorage.cookies) {
        if (![cookie.name isEqualToString:@"__cust"]) {
        NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 3);", cookie.name, cookie.value];
        [JSCookieString appendString:excuteJSString];
        }
        }

        //执行js
        [webView evaluateJavaScript:JSCookieString completionHandler:^(id obj, NSError * _Nullable error) {
        //加载真正的第一次Request
        [self loadRealRequest];
        }];
        }
        }
      • 最晴天:我不知道是哪里的问题,WKWebView始终没有携带上cookie

      本文标题:【iOS】WKWebView使用Cookies遇到的坑

      本文链接:https://www.haomeiwen.com/subject/qkyhwttx.html