一、cookie简单介绍
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会自动的被浏览器存储然后在下一次请求该服务器网络资源时又返还给该服务器,Cookie 又称之 web Cookie 或 browser cookie
cookie 的使用场景:
Session管理: 如 登录、购物车、积分、或者如何有浏览器操作记录并同时应记录在服务端的数据。
个性设置 如:用户偏好、web主题、其他个性设置
跟踪 如:记录和分析用户习惯
最常见的就是使用cookie作状态验证,由于Http通信是无状态的,在登录页面设计的时候,往往会利用cookie 作为浏览器状态信息来确认当前用户是否处于登陆状态。
当一个浏览器向一个服务器发送请求的时候该服务器就会在response的时候自动发送Set-Cookie header给浏览器,一般如下:
Set-Cookie: key=value
Set-Cookie: key=value; Expires=<date>
Set-Cookie: key=value; Max-Age=<non-zero-digit>
Set-Cookie: key=value; Secure
Set-Cookie: key=value;; HttpOnly
浏览器端 Cookie类型分为 session cookie 和 permanent cookie,他们之间的区别在于前者只要在浏览器关闭后 cookie就会消失,而permanent cookie则不会,二者设置的区别在于cookie设置的时候是否添加了expires 或者 max-age 属性。
另外还有一点值得注意的是cokie的安全问题,因为浏览器端用户可以直接调用document.cookie 取 cookie,所以在这里恶意代码也可以轻易通过 documen.cookie 获取到当前用户cookie中的敏感信息,如 UserId、token等。所以在应用设计时有些 cookie要设置为 httpOnly cookie。这样cookie就只能被服务端读取,而浏览器获取不到任何cookie 信息。所以如果是token这类的敏感信息,绝不能存储在http cookie中,因为很容易受到劫持。原则上,cookie设置https 读取是起码的规定,如果可以的话,最好放置在httpOnly cookie中。
Cookie 的几个字端详解
域 Domain
指定了哪些主机可以接受 cookie,如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了Domain,则一般包含子域名(子域名可以访问父域名的cookie)
例如:如果设置 Domain = hhh.org,则 cookie也包含在子域名中(如:develop.hhh.org)
Path
Path 标示指定了主机下的哪些路径可以接受cookie (该URL路径必须存在于请求URL中)。以字符 %x2F (/)作为路径分隔符,子路径也会被匹配。
设置 Path = /docs ,则以下地址都会匹配
/docs
/docs/web
/docs/web/http
Expires / Max-Age
Cookie 的过期时间,过了这个时间之后cookie 将会自动删除。
Set-Cookie: id=a3fWa; Expires=Fri, 21 Oct 2019 07:28:00 GMT;
Max-Age 的单位是秒
document.cookie = 'promo_shown=1; Max-Age=2600000; Secure'
HttpOnly
为了避免跨域脚本(XSS)攻击,通过JavaScript 的docment.cookie API 无法访问带有HttpOnly 标记的cookie,它们只应该发送给服务器。如果包含服务端session信息的 Cookie 不想被客户端javaScript 脚本调用,那么就应该为其设置 HttpOnly 标记。
Set-Cookie: id=a3fOa; Expires=Fri, 21 Oct 2019 07:28:00 GMT; Secure; HttpOnly
Secure
标记为secure 的cookie 只应通过被Https 协议加密过的请求发送给服务端。
SameSite
SameSite Cookie 允许服务器要求某个cookie 在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击。
Set-Cookie: key=value; SameSite=Strict
其可选取值:
None : 浏览器会在同站请求、跨站请求下继续发送Cookies,不区分大小写;
Strict :浏览器将只发送相同站点请求的cookie (即当前网页URL与请求目标URL完全一致)。如果请求来自与当前location的URL 不同的URL,则不包括标记为Strict属性的Cookie;
Lax :在新版浏览器中,为默认选项,Same-site Cookie 将会为一些跨站请求保留。
二、WKWebView 中的 cookie问题
由于WK整个框架的改变,WK已经不单单是在App 这一侧,WK容器发起的H5请求已经不在App进程中发起和响应处理,而是在专门的web进程中处理,所以WK的网络请求无法直接从NSHTTPCookieStorage取到cookie,从而导致当H5访问一些带鉴权的接口就会出现问题。
而原来UIWebView 作为H5的容器,后台下发的cookie会自动存储到NSHTTPCookieStrorage这个容器中,webView 内部的请求会自动从NSHTTPCookieStorage获取合适的cookie带上去。而WK内有一个自己独立的cookie的容器,因为这个容器与App侧的NSHttpCookieStorage里的cookie的同步是进程级别的同步,并且是单向的,这就导致WK第一次请求无法及时没有带上App侧的cookie。
腾讯一个权威团队在解决WK的cookie 建议我们采用这种思路:通过key-Value构造一个cookie,WK loadRequest前,在request header 中设置 cookie ,解决首个请求cookie 带不上的问题,通过 docment.cookie 设置Cookie解决后续页面(同域)Ajax、ifFrame请求的,下面我们这里也是建议大家采用这种思路来解决cookie的问题。
首先我们在WK在发第一个request请求时,向request里设置好cookie,确保我们的请求在服务器端那边收到时是可以签权通过的,看下面代码:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//request携带
[request setHTTPShouldHandleCookies:YES];
//cookie直接设置name,value
[request setValue:[NSString stringWithFormat:@"%@=%@;",@"name", @"lisi"] forHTTPHeaderFie
ld:@"Cookie"];
注意: 这里我们是直接设置cookie的name,value,这样设置cookie显然是不够的,这样做你可能创建出多个相同名字的cookie ,另外如果只是简单构造key-value,可能还会导致因为同源策略的请求带不上cookie。正确的方式应该是要求我们构造出一个完整的cookie串,看下面代码:
//构造完整的cookie串
NSHTTPCookie *cookie = [DCookieViewController cookieWithDomain:url.host name:@"name" value:@"lisi2"];
[request setValue:[NSString stringWithFormat:@"%@;",[self_getCookieString:cookie]] forHTTPHeaderField:@"Cookie"];
- (NSString *)_getCookieString:(NSHTTPCookie *)cookie
{
NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;expiresDate=%@;path=%@;sessionOnly=%@;isSecure=%@",cookie.name,cookie.value,cookie.domain,cookie.expiresDate,cookie.path ?: @"/",cookie.isSecure ? @"TRUE":@"FALSE",cookie.sessionOnly ? @"TRUE":@"FALSE"];
return string;
}
除了我们要在request 里设置好cookie ,保证App的WK 这边发出去的请求,在服务端那边是认可以外,我们还需要保证客户端的H5那边也能拿到我们App里的Cookie,那我们就需要另外再做一件事了,就是用JS脚本注入的方式去设置,保证H5那边也能拿到我们App原生的cookie。
注入JS脚本设置cookie
//通过document.cookie设置Cookie
WKUserContentController* userContentController = [[WKUserContentController alloc] init];
WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource: @"document.cookie = 'name=lisi_handsome';" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrame
Only:NO];
[userContentController addUserScript:cookieScript];
configuration.userContentController = userContentController;
另外如果我们要把NSHTTPCookieStorage 的cookie也带过去,可以采用以下方法:
//js方式设置cookie
- (NSString *)setCcookieJS
{
NSMutableString *jsScript = [NSMutableString string]; [jsScript appendString:@"var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } );\n"];
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
if ([cookie.value rangeOfString:@"'"].location != NSNotFound)
{
continue;
}
NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",cookie.name,cookie.value, cookie.domain,cookie.path ?: @"/"];
[jsScript appendFormat:@"if (cookieNames.indexOf('%@') == -1)
{
document.cookie='%@'; };\n", cookie.name, string];
}
return jsScript;
}