前言
首先这篇文章所描述的仅仅只是cookie中的Session字段的问题。
我理解的Session 对象是存储特定的用户会话所需的配置信息。当用户在Web页面跳转的时候,Session不会丢失,会一直存在下去。用户请求到Web页面时,如果用户还没有会话,则服务器将自动创建一个,它将被存储于cookie中。
我开发中所遇到的问题是,某些H5业务需要验证登录后才可以使用,并且还要带一个特殊的cookie字段表示来源。这就要求Session在WebView中可以一直保持着,否则没有Session,服务器每次都需要在验证一次用户信息。
WKWebView
最开始我使用WKWebView,毕竟效率高了很多,但是在cookie这块确实是很坑。
Cookie并不会再加载后自动保存到NSHTTPCookieStorage中,也就是说,发起请求的时候不会自动带上存储于NSHTTPCookieStorage容器中的Cookie,内存根本不用UIWebView那套了。可能是有自己的私有存储吧(不太清楚)。
在这里看到,可以使用同一个的WKProcessPool实例来使不同的WKWebView拥有相同的Cookie。
/**
构建一个单例用来保存WKProcessPool
*/
+ (instancetype)shareInstance{
static WKProcesspoolManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[WKProcesspoolManager alloc] init];
});
return instance;
}
- (instancetype)init {
self.wkProcessPool = [[WKProcessPool alloc] init];
}
我经过这样尝试后发现,还是不能解决不同WKWebView中session保持的问题,于是我就想看看在WKWebView中的Cookie到底保存了什么字段。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response;
NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:response.allHeaderFields forURL:[NSURL URLWithString:_urlStr]];
for (NSHTTPCookie *cookie in cookies) {
NSLog(@"cookie = %@",cookie);
}
decisionHandler(WKNavigationResponsePolicyAllow);
}
拿到Cookie后发现,里面竟然只能获取到我自己在请求时设置的字段。其他的根本就没有。而Sessionid这个Cookie 可能是被服务器设置了HttpOnly属性了,WKWebView将它保护起来。所以想要修改它,JS是不可能的。只能通过WKWebView的接口入手了,可是我并没有发现这样的接口。所以我只能换回UIWebView了。
UIWebView
换回来之后,其实一切都很简单了。
UIWebView中的请求之后Cookie都保存在NSHTTPCookieStorage中了,并且sessionid也存在。
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSHTTPCookieStorage *myCookie = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [myCookie cookies]) {
NSLog(@"%@", cookie);
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
}
}
这样可以将完整的Cookie保存下来,以便自动登录。接下来就是将保存起来的cookis设置在request中就可以了
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL
URLWithString:HOST]]; // HOST就是你web服务器的域名地址
// 设置header
for (NSHTTPCookie *cookie in cookies){
// cookiesWithResponseHeaderFields方法,需要为URL设置一个cookie为NSDictionary类型的header,注意NSDictionary里面的forKey需要是@"Set-Cookie"
NSString *str = [[NSString alloc] initWithFormat:@"%@=%@",[cookie name],[cookie value]];
NSDictionary *dic = [NSDictionary dictionaryWithObject:str forKey:@"Set-Cookie"];
NSArray *headeringCookie = [NSHTTPCookie cookiesWithResponseHeaderFields:dic forURL:[NSURL URLWithString:HOST]];
// 设置Cookie,只要访问URL为HOST的网页时,会自动附带上设置的header
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:headeringCookie
forURL:[NSURL URLWithString:HOST]
mainDocumentURL:nil];
}
这样就可以在不同的UIWebView中共享同一个sessionid了。
最后
在换回UIWebView后,我测试了一下。在第一个页面设置sessionid然后第二个页面读取sessionid,是可以读取到的。
然后我又使用了WKWebView测试发现,还是获取不到。
我又想到微信的WebView内核好像全是WKWebView了,于是我在微信中打开测试了一下,发现竟然可以获取到到sessionid。
也就是说微信做到了。果然还是有方法的。只是自己水平不足罢了。希望有大牛能提点我一下,也希望这篇文章给予有需要的人一点帮助。
2017-09-05 更新
-----------------------------------------------------------------------------------------------
上个月,全面使用了WKWebView了,也解决sessionid的问题。
具体解决过程如下:
- 首先在登录的时候,在网络请求结束的地方用一个单例来保存这次cookie。大概代码:
WKCookieSyncManager *cookiesManager = [WKCookieSyncManager sharedWKCookieSyncManager];
[cookiesManager setCookie];
@interface WKCookieSyncManager () <WKNavigationDelegate>
@property (nonatomic, strong) WKWebView *webView;
///用来测试的url这个url是不存在的
@property (nonatomic, strong) NSURL *testUrl;
@end
@implementation WKCookieSyncManager
+ (instancetype)sharedWKCookieSyncManager {
static WKCookieSyncManager *sharedWKCookieSyncManagerInstance = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedWKCookieSyncManagerInstance = [[self alloc] init];
});
return sharedWKCookieSyncManagerInstance;
}
- (void)setCookie {
//判断系统是否支持wkWebView
Class wkWebView = NSClassFromString(@"WKWebView");
if (!wkWebView) {
return;
}
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.processPool = self.processPool;
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:self.testUrl];
self.webView.navigationDelegate = self;
[self.webView loadRequest:request];
}
#pragma - get
- (WKProcessPool *)processPool {
if (!_processPool) {
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
_processPool = [[WKProcessPool alloc] init];
});
}
return _processPool;
}
- (NSURL *)testUrl {
if (!_testUrl) {
NSURLComponents *urlComponents = [NSURLComponents new];
urlComponents.host = @"tm.lilanz.com";
urlComponents.scheme = @"http";
urlComponents.path = @"a.aspx";
// NSLog(@"测试url=%@", urlComponents.URL);
return urlComponents.URL;
}
return _testUrl;
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
//取出cookie
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in cookieStorage.cookies) {
NSLog(@"name = %@ value = %@",cookie.name,cookie.value);
}
//js函数
NSString *JSFuncString =
@"function setCookie(name,value,expires)\
{\
var oDate=new Date();\
oDate.setDate(oDate.getDate()+expires);\
document.cookie=name+'='+value+';expires='+oDate;\
}\
function getCookie(name)\
{\
var arr = document.cookie.match(new RegExp('(^| )'+name+'=([^;]*)(;|$)'));\
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:nil];
}
注:或者在其它有网络请求的地方,但是这个地方必须先执行于你要使用的网页之前。
然后在加载WKWebView的地方使用即可。
WKCookieSyncManager *cookiesManager = [WKCookieSyncManager sharedWKCookieSyncManager];
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.processPool = cookiesManager.processPool;
_wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];