iOS开发实战 - Cookie注入

Cookie注入的使用场景:

在开发中,我们常常会遇到这样一种场景:通过在一部分网络请求中注入Cookie信息让后台动态校验当前用户的登录状态以及用户权限。
  1. 在APP中打开一个需要登录用户才能看的页面,一般客户端会先判断是否登录,如果没有登录去登录。缺点每次都要判断,如果是付费内容,还要引导用户去支付,这些都要向后台发起多个请求,还要判断用户身份,增加了网络开销,如果逻辑处理的不够严谨,很容易出错。
  2. Cookie的注入可以解决上面的问题,一次请求将用户信息发送给后台,让后台判断给你什么数据,你只用按约定判断返回的字段,做出相应的处理即可,这样减少了用户请求的次数,减轻了服务器的负载,也省了用户的流量。
第一步:获取登录成功的请求标识:如:PHPSESSID=495njbid8as3o79bjqh1mocl22

注:"PHPSESSID"这个字段名对于不同的服务器,配置不同名称也不同,你可以询问后台,不过我建议还是自己去抓包查看,这里我使用的抓包工具是:Charles。

一般客户端会在登录成功的回调里面获取Set-Cookie,对Set-Cookie处理获取内部的"PHPSESSID=495njbid8as3o79bjqh1mocl22"
我们先通过抓包工具来看看登录成功返回的信息:


登录成功抓包
1. 查看一下Cookie信息(包含Set-Cookie信息)
Cookie信息

注意:这里我们可以看到三条Set-Cookie信息,这是服务器端告诉你下次请求的时候需要在请求头中加入这三条信息。只是第二条和第三条是用户的账户名和经过MD5加密的密码,这个我们就是我们上传给服务器的,不用提取,不过需要按照"topmasteruserName=177****9679;"这种服务器要求的格式拼接Cookie字符串。

2. 我们再来看看客户端获取的Set-Cookie信息(请求响应头中的三条Set-Cookie拼接而成)

2.1响应头中的Set-Cookie的条数是由服务端决定的,一般是一个key=value对应一条;

2.2 每次获取到的Set-Cookie字符串中它们的排列顺序不同:


获取Set-Cookie
3. 得到这个字符串后,就是想办法提取PHPSESSID=495njbid8as3o79bjqh1mocl22了(后面的path=/等不需要)
提取PHPSESSID
4. 保存信息到本地
保存用户信息
5. 拼接Cookie字符串
拼接Cookie字符串
6. 到这里需要向服务器注入的Cookie已经准备好了,只需要在发起请求的时候设置请求头的Cookie即可;
(1)AFNetworking 注入Cookie
[manager.requestSerializer setValue:[FunctionFast getCookieFromUserDefault] forHTTPHeaderField:@"Cookie"];
(2)UIWebView中请求注入Cookie
//首次请求注入cookie
- (void)requestData {
    NSURL *url = [NSURL URLWithString:self.url];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    // 注入Cookie
    [request setValue:[FunctionFast getCookieFromUserDefault] forHTTPHeaderField:@"Cookie"];
    [self.webView loadRequest:request];
}
//该webview后续请求url时(同域)调用
- (void)loadRequestWithUrlString:(NSString *)urlString {
    // 在此处获取返回的cookie
    NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
    NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    
    for (NSHTTPCookie *cookie in [cookieStorage  cookies]) {
        [cookieDic setObject:cookie.value forKey:cookie.name];
    }
    // cookie重复,先放到字典进行去重,再进行拼接
    for (NSString *key in cookieDic) {
        NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
        [cookieValue appendString:appendString];
    }
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
    [request addValue:cookieValue forHTTPHeaderField:@"Cookie"];
    [self.wkWebView loadRequest:request];
}
(3)WKWebView中请求注入Cookie

参考资料:http://www.jianshu.com/p/541d46671448

WKWebView相比于UIWebView:(iOS8.0+)
①WKWebView 是苹果在 WWDC 2014 上推出的新一代 webView 组件,用以替代 UIKit 中笨重难用、内存泄漏的 UIWebView;WKWebView 拥有60fps滚动刷新率、和 safari 相同的 JavaScript 引擎等优势。
②速度快了一倍,内存却减少为原来的一半
③cookie不再是自动携带,需要手动设置
④交互更加顺畅,比如app底部四个tabBar也都是网页的,在UIWebView下点击,整个H5页都会闪白一下,但是在WKWebView下点击,四个tabBar效果与原生app效果更加类似,不会有闪白现象;
⑤WKWebView 自诩拥有更快的加载速度,更低的内存占用,但实际上 WKWebView 是一个多进程组件,Network Loading 以及 UI Rendering 在其它进程中执行。初次适配 WKWebView 的时候,我们也惊讶于打开 WKWebView 后,App 进程内存消耗反而大幅下降,但是仔细观察会发现,Other Process 的内存占用会增加。在一些用 webGL 渲染的复杂页面,使用 WKWebView 总体的内存占用(App Process Memory + Other Process Memory)不见得比 UIWebView 少很多。
⑥在 UIWebView 上当内存占用太大的时候,App Process 会 crash;而在 WKWebView 上当总体的内存占用比较大的时候,WebContent Process 会 crash,从而出现白屏现象。在 WKWebView 中加载下面的测试链接可以稳定重现白屏现象:
http://people.mozilla.org/~rnewman/fennec/mem.html
这个时候 WKWebView.URL 会变为 nil, 简单的 reload 刷新操作已经失效,对于一些长驻的H5页面影响比较大。
⑦增减了一些代理方法,更方便的进行协议拦截和进度条展示

补充:

业界普遍认为 WKWebView 拥有自己的私有存储,不会将 Cookie 存入到标准的 Cookie 容器NSHTTPCookieStorage中。
实践发现 WKWebView 实例其实也会将 Cookie 存储于 NSHTTPCookieStorage 中,但存储时机有延迟,在iOS 8上,当页面跳转的时候,当前页面的 Cookie 会写入 NSHTTPCookieStorage 中,而在 iOS 10 上,JS 执行 document.cookie 或 服务器 Set-Cookie 注入的 Cookie 会很快同步到 NSHTTPCookieStorage 中,FireFox 工程师曾建议通过 reset WKProcessPool 来触发 Cookie 同步到 NSHTTPCookieStorage 中,实践发现不起作用,并可能会引发当前页面 session cookie 丢失等问题。

WKWebView Cookie 问题在于 WKWebView 发起的请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie。
WKProcessPool

苹果开发者文档对 WKProcessPool 的定义是:A WKProcessPool object represents a pool of Web Content process. 通过让所有 WKWebView 共享同一个 WKProcessPool 实例,可以实现多个 WKWebView 之间共享 Cookie(session Cookie and persistent Cookie)数据。不过 WKWebView WKProcessPool 实例在 app 杀进程重启后会被重置,导致 WKProcessPool 中的 Cookie、session Cookie 数据丢失,目前也无法实现 WKProcessPool 实例本地化保存。

由于许多 H5 业务都依赖于 Cookie 作登录动态校验,而 WKWebView 上请求不会自动携带 Cookie, 目前的主要解决方案是:

!!添加之前(登录时)保存好的cookie或者拼接cookie所需的相关字符串
第一种:

和上面的UIWebView一样, 在loadRequest 前,在 request header 中设置 Cookie, 解决首个请求 Cookie 带不上的问题;

- (void)requestData {
    NSURL *url = [NSURL URLWithString:self.url];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    // 注入Cookie
    [request setValue:[FunctionFast getCookieFromUserDefault] forHTTPHeaderField:@"Cookie"];
    [self.wkWebView loadRequest:request];
}

//该webview调用其他url时(同域)调用
- (void)loadRequestWithUrlString:(NSString *)urlString {
    // 在此处获取返回的cookie
    NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
    NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    
    for (NSHTTPCookie *cookie in [cookieStorage  cookies]) {
        [cookieDic setObject:cookie.value forKey:cookie.name];
    }
    // cookie重复,先放到字典进行去重,再进行拼接
    for (NSString *key in cookieDic) {
        NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
        [cookieValue appendString:appendString];
    }
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
    [request addValue:cookieValue forHTTPHeaderField:@"Cookie"];
    [self.wkWebView loadRequest:request];
}
第二种:

通过 document.cookie 设置 Cookie 解决后续页面(同域)Ajax、iframe 请求的 Cookie 问题(注意:document.cookie() 无法跨域设置 cookie

补充:

比如,第一个请求是 www.a.com,我们通过在 request header 里带上 Cookie 解决该请求的 Cookie 问题,接着页面跳转到 www.b.com,这个时候 www.b.com 这个请求就可能因为没有携带 cookie 而无法访问。当然,由于每一次页面跳转前都会调用回调函数:
— (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
可以在该回调函数里拦截请求,copy request,在 request header 中带上 cookie 并重新 loadRequest。不过这种方法依然解决不了页面 iframe 跨域请求的 Cookie 问题,毕竟-[WKWebView loadRequest:] 只适合加载 mainFrame 请求。

//注入cookie: 在创建WKWebView之前的时候将cookie信息存放到WKUserScript中
- (void)injectCookie {
    //创建webview配置对象
    WKWebViewConfiguration *webConfig = [[WKWebViewConfiguration alloc] init];
    // 设置偏好设置
    webConfig.preferences = [[WKPreferences alloc] init];
    // 默认为0
    webConfig.preferences.minimumFontSize = 10;
    // 默认认为YES
    webConfig.preferences.javaScriptEnabled = YES;
    // 在iOS上默认为NO,表示不能自动通过窗口打开
    webConfig.preferences.javaScriptCanOpenWindowsAutomatically = NO;
    // web内容处理池
    webConfig.processPool = [[WKProcessPool alloc] init];
    // 将所有cookie以document.cookie = 'key=value';形式进行拼接
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
#warning 然而这里的单引号一定要注意是英文的
    //格式  @"document.cookie = 'key1=value1';document.cookie = 'key2=value2'";
    NSString *cookie = [NSString stringWithFormat: @"document.cookie = '%@';document.cookie = 'topmasteruserName=%@';document.cookie = 'topmasteruserCode=%@;'", [defaults valueForKey:@"PHPSESSID"], [defaults valueForKey:@"loginName"], [defaults valueForKey:@"ticket"]];
    
    // 加cookie给h5识别,表明在iOS端打开该地址
    WKUserContentController* userContentController = WKUserContentController.new;
    WKUserScript * cookieScript = [[WKUserScript alloc]
                                   initWithSource: cookie
                                   injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    [userContentController addUserScript:cookieScript];
    webConfig.userContentController = userContentController;
    
    self.wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) configuration:webConfig];
    [self.view addSubview:_wkWebView];
    _wkWebView.UIDelegate = self;
    _wkWebView.navigationDelegate = self;
}
//该webview中调用其他url时(同域)调用
- (void)loadRequestWithUrlString:(NSString *)urlString {
    // 在此处获取返回的cookie
    NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
    NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    
    for (NSHTTPCookie *cookie in [cookieStorage  cookies]) {
        [cookieDic setObject:cookie.value forKey:cookie.name];
    }
    // cookie重复,先放到字典进行去重,再进行拼接
    for (NSString *key in cookieDic) {
        NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
        [cookieValue appendString:appendString];
    }

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
    [request addValue:cookieValue forHTTPHeaderField:@"Cookie"];
    [self.wkWebView loadRequest:request];
}

到这里已经满足基本需求了,如有错误的地方,欢迎亲们指正。

亲们喜欢的话,请给个 ❤️ 额,谢谢亲们的支持!!!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268