cookie
或者token
去请求数据。由于公司项目比较老,里面既用了ASIHTTPRequest
框架,又用了AFNetworking
框架,发现其实这两个框架默认都是自动保持cookie
的,我们不用去刻意处理它(获取与上传)。但是由于想搞清楚还是研究了一下cookie
,并用NSURLSession
、ASIHTTPRequest
、 AFNetworking
自己手动管理cookie
。cookie
: 是网站服务端为了辩别用户身份,在服务器端生生成并存储在用户本地终端(电脑、手机)上的数据。其实cookie
主要是用来免密登录的,我们这只是通过用户名和密码获取一个身份令牌,用于后面的接口调用,更像是token
。NSHTTPCookieStorage
提供了管理所有NSHTTPCookie
对象的接口,在OS X里,cookie
是在所有程序中共享的,而在iOS
中,cookie
只在当当前应用中有效。。Session Cookie(SessionOnly返回YES的Cookie)只能在单一进程中使用。NSHTTPCookieStorage
可以获取,删除,设置单例里面的cookies
,设置cookie
的管理策略等。//只读的单例对象
@property(class, readonly, strong) NSHTTPCookieStorage *sharedHTTPCookieStorage;
//获取里面的cookie对象数组
@property (nullable , readonly, copy) NSArray *cookies;
//添加cookie
- (void)setCookie:(NSHTTPCookie *)cookie;
//删除cookie
- (void)deleteCookie:(NSHTTPCookie *)cookie;
//删除某个日期之前的cookie
- (void)removeCookiesSinceDate:(NSDate *)date API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
//设置cookie的管理策略
@property NSHTTPCookieAcceptPolicy cookieAcceptPolicy;
//获取对应url返回的cookie数组
- (nullable NSArray *)cookiesForURL:(NSURL *)URL;
NSHTTPCookie
里面有个properties
的属性,其组成如下:<__NSArrayM 0x283bec540>(
<NSHTTPCookie
version:0
name:JSESSIONID
value:A0787301B667F5E1C7B5BABAAE58B104
expiresDate:'(null)'
created:'2018-12-27 02:39:06 +0000'
sessionOnly:TRUE
domain:192.168.1.121
partition:none
sameSite:none
path:/inner
isSecure:FALSE
isHTTPOnly: YES
path:"/inner" isSecure:FALSE isHTTPOnly: YES>,
<NSHTTPCookie
version:0
name:token
value:1545878346436_111_cd90cdd1-9938-4e18-8ccd-79ad54d5527c
expiresDate:'2019-01-26 02:39:06 +0000'
created:'2018-12-27 02:39:06 +0000'
sessionOnly:FALSE
domain:192.168.1.121
partition:none
sameSite:none
path:/inner
isSecure:FALSE
path:"/inner" isSecure:FALSE>
)
name | 必选 | 规定 cookie 的名称 |
---|---|---|
value | 必选 | 规定 cookie 的值 |
expire | 可选 | 规定 cookie 的有效期 |
path | 可选 | 规定 cookie 的服务器路径,只有该路劲下的文件接口才能使用 |
domain | 可选 | 规定 cookie 的域名 |
secure | 可选 | 规定是否通过安全的 HTTPS 连接来传输 cookie |
其中sessionOnly:TRUE
的cookie是后台服务器自动创建的。
cookie
和session
默认都是后台创建,后端会将cookie
放在在response
的header
中返回给前端,将session
缓存在服务器中;前端获取到cookie
可以自己手动保存,也可以交给NSHTTPCookieStorage
单例来保存;前端将cookie
放在request
的header
中传给服务器,服务器会去查对应的session
,然后去交换获得用户信息(用户登录ID等)请求接口,这就是整个cookie
获取、设置和请求数据的流程。cookie
的几种方法: //方式1 : NSHTTPCookie获取cookie
NSArray * array = [NSHTTPCookie cookiesWithResponseHeaderFields:httpresponse.allHeaderFields forURLresponse.URL];
for (NSHTTPCookie * cookie in array) {
//保存到sharedHTTPCookieStorage中
[[NSHTTPCookieStorage sharedHTTPCookieStorage]setCookie:cookie];
}
//方式2:NSHTTPURLResponse获取
NSHTTPURLResponse * httpresponse = (NSHTTPURLResponse *)response;
NSDictionary * dic = httpresponse.allHeaderFields;
//获取cookie字符串
NSString * cookiesStr = [dic valueForKey:@"Set-Cookie"];
//方式3:NSHTTPCookieStorage获取cookie
NSArray * cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage]cookiesForURL:response.URL];
NSDictionary * dic = httpresponse.allHeaderFields;
返回的字典有个"Set-Cookie"的字符串值就是多个cookie的
默认样式,我们获取到后可以直接设置cookie
到request的 header
中。{
"Cache-Control" = "no-cache,must-revalidate";
"Content-Length" = 7;
Date = "Thu, 27 Dec 2018 07:11:51 GMT";
Expires = "Thu, 01 Jan 1970 00:00:00 GMT";
Pragma = "no-cache";
Server = "Apache-Coyote/1.1";
"Set-Cookie" = "JSESSIONID=D6F65DA599B3B6B22C1927C5D85B53F0; Path=/inner; HttpOnly, token=1545894711778_111_17906b71-9c77-48c5-8e5a-b9e52522a538; Expires=Sat, 26-Jan-2019 07:11:51 GMT; Path=/inner";
}
NSString * cookiesStr = [dic valueForKey:@"Set-Cookie"];
//设置cookies
[request setValue:cookiesaStr forHTTPHeaderField:@"Cookie"];
设置cookie方式2:构建多个NSHTTPCookie
实例对象的数组,根据NSHTTPCookie
实例数组生成对应的HTTP cookie header
,设置cookie
到request
的header
中。
NSDictionary *properties1 = [NSDictionary dictionaryWithObjectsAndKeys:
@"domain.com", NSHTTPCookieDomain,
@"/", NSHTTPCookiePath,
@"userid", NSHTTPCookieName,
strUserId, NSHTTPCookieValue,
nil];
NSDictionary *properties2 = [NSDictionary dictionaryWithObjectsAndKeys:
@"domain.com", NSHTTPCookieDomain,
@"/", NSHTTPCookiePath,
@"pid", NSHTTPCookieName,
pid, NSHTTPCookieValue,
nil];
NSHTTPCookie *cookie1 = [NSHTTPCookie cookieWithProperties:properties1];
NSHTTPCookie *cookie2 = [NSHTTPCookie cookieWithProperties:properties2];
NSArray* cookies = [NSArray arrayWithObjects: cookie1, cookie2, nil];
//根据NSHTTPCookie实例数组生成对应的HTTP cookie header
NSDictionary * headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
//设置cookie到header中
request.allHTTPHeaderFields = headers;
NSURLSession
中有两个+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
初始化方法,这个configuration
可以给NSURLSession
实例对象配置一下属性策略等,与cookie
有关的属性有下://发送请求时是否设置cookie
@property BOOL HTTPShouldSetCookies;
//设置cookie的接收策略
@property NSHTTPCookieAcceptPolicy HTTPCookieAcceptPolicy;
typedef NS_ENUM(NSUInteger, NSHTTPCookieAcceptPolicy) {
NSHTTPCookieAcceptPolicyAlways,//接收所有的cookie,默认策略.
NSHTTPCookieAcceptPolicyNever,//不接收所有的cookie
NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain//只接收main document domain中的cookie.
};
//保存设置cookie的,如果不设置默认是[NSHTTPCookieStorage sharedHTTPCookieStorage],设置为nil管理cookie
@property (nullable, retain) NSHTTPCookieStorage *HTTPCookieStorage;
所以NSURLSession
多了一种设置cookie的方式,只要设置了HTTPCookieStorage
属性就可以自动设置了cookie。
我们请求完成后必须手动将cookie
保存到[NSHTTPCookieStorage sharedHTTPCookieStorage]
中,不会自动将响应体header中的cookie保存,下面是具体实例。
//获取cookie
- (void)getCookies1{
NSURL * url = [NSURL URLWithString:@"http://192.168.1.121:8080/inner/mobile/actionSh/Abc!login.action?"];
NSString * post = [NSString stringWithFormat:@"loginId=%@",@"111"];
NSData * postData = [post dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = postData;
//defaultSessionConfiguration 使用默认session配置,类似NSURLConnection的标准配置,使用硬盘来存储缓存数据,会将缓存、cookie等存在本地
//ephemeralSessionConfiguration 临时session配置,与默认配置相比,不使用永久持存cookie、证书、缓存的配置,最佳优化数据传输。
//backgroundSessionConfiguration 后台session配置,与默认配置类似,不同的是会在后台开启另一个线程来处理网络数据
NSURLSessionConfiguration * defultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
//不允许设置cookie
defultConfiguration.HTTPShouldSetCookies = NO;
//设置不允许缓存cookiechelue
defultConfiguration.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
//清除所有的cookie
for(NSHTTPCookie *cookie in [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies)
{
[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie: cookie];
}
NSLog(@"清除所有的cookies : %@",[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies);
defultConfiguration.HTTPCookieStorage = nil;
NSURLSession * session = [NSURLSession sessionWithConfiguration:defultConfiguration delegate:nil delegateQueue:[NSOperationQueue currentQueue]];
NSURLSessionTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"请求完成!!!");
if (!error) {
NSString * result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
if (result) {
NSLog(@"解析成功:%@",result);
NSLog(@"response:%@",response);
NSHTTPURLResponse * httpresponse = (NSHTTPURLResponse *)response;
NSDictionary * dic = httpresponse.allHeaderFields;
// NSString * cookies = [dic valueForKey:@"Set-Cookie"];
// NSLog(@"cookies : %@ --- %@",cookies,[NSThread currentThread]);
// NSArray * NSArray = [[NSHTTPCookieStorage sharedHTTPCookieStorage]cookiesForURL:response.URL];
//获取cookie
NSArray * array = [NSHTTPCookie cookiesWithResponseHeaderFields:dic forURL:response.URL];
for (NSHTTPCookie * cookie in array) {
//保存到sharedHTTPCookieStorage中
[[NSHTTPCookieStorage sharedHTTPCookieStorage]setCookie:cookie];
}
NSLog(@"NSHTTPCookieStorage : %@",[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies);
}else{
NSLog(@"解析失败!!!");
}
}else{
NSLog(@"请求失败 : %@",error.localizedDescription);
}
}];
[task resume];
}
//设置cookie
- (void)requestWithCookies1{
NSString * urlStr = @"http://192.168.1.121:8080/inner/mobile/actionSh/Abc!getData.action?";
//encode去掉中午和特殊字符
urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL * url = [NSURL URLWithString:urlStr];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSURLSessionConfiguration * defultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
defultConfiguration.HTTPShouldSetCookies = YES;
defultConfiguration.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyAlways;
NSLog(@"NSHTTPCookieStorage : %@",[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies);
defultConfiguration.HTTPCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSURLSession * session = [NSURLSession sessionWithConfiguration:defultConfiguration delegate:nil delegateQueue:[NSOperationQueue currentQueue]];
NSURLSessionTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"请求完成!!!");
if (!error) {
NSString * result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
if (result) {
NSLog(@"解析成功:%@",result);
}else{
NSLog(@"解析失败!!!");
}
}else{
NSLog(@"请求失败 : %@",error.localizedDescription);
}
}];
[task resume];
}
上面的方法有时候并不能立即将cookie设置进去,所以我们自己手动管理cookie
比较保险,就是在request
的header
中设置cookie
,在response
的header
中获取到得cookie
保存到偏好设置或单例中自己管理。
//获取cookie
- (void)getCookies2{
NSURL * url = [NSURL URLWithString:@"http://192.168.1.121:8080/inner/mobile/actionSh/Abc!login.action?"];
NSString * post = [NSString stringWithFormat:@"loginId=%@",@"111"];
NSData * postData = [post dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = postData;
NSURLSessionConfiguration * defultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
//不允许设置cookie
defultConfiguration.HTTPShouldSetCookies = NO;
//不接受cookie
defultConfiguration.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
NSURLSession * session = [NSURLSession sessionWithConfiguration:defultConfiguration delegate:nil delegateQueue:[NSOperationQueue currentQueue]];
NSURLSessionTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"请求完成!!!");
if (!error) {
NSString * result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
if (result) {
// NSArray * cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage]cookiesForURL:response.URL];
NSLog(@"解析成功:%@",result);
NSLog(@"response:%@",response);
NSHTTPURLResponse * httpresponse = (NSHTTPURLResponse *)response;
NSDictionary * dic = httpresponse.allHeaderFields;
//获取cookie字符串
NSString * cookiesStr = [dic valueForKey:@"Set-Cookie"];
NSData * cookiesData = [NSKeyedArchiver archivedDataWithRootObject:cookiesStr];
//保存到偏好设置中
[[NSUserDefaults standardUserDefaults] setValue:cookiesData forKey:@"BoncUserDefaultsCookie"];
[[NSUserDefaults standardUserDefaults]synchronize];
}else{
NSLog(@"解析失败!!!");
}
}else{
NSLog(@"请求失败 : %@",error.localizedDescription);
}
}];
[task resume];
}
//设置cookie
- (void)requestWithCookies2{
NSString * urlStr = @"http://192.168.1.121:8080/inner/mobile/actionSh/Abc!getData.action?";
//encode去掉中午和特殊字符
urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL * url = [NSURL URLWithString:urlStr];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
NSData * cookiesData = [[NSUserDefaults standardUserDefaults] valueForKey:@"BoncUserDefaultsCookie"];
NSString * cookiesaStr = [NSKeyedUnarchiver unarchiveObjectWithData:cookiesData];
//设置cookies
[request setValue:cookiesaStr forHTTPHeaderField:@"Cookie"];
//如果是NSURLRequest,可用下面方式设置
// request.allHTTPHeaderFields = nil;
NSURLSessionConfiguration * defultConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
defultConfiguration.HTTPShouldSetCookies = NO;
defultConfiguration.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
NSURLSession * session = [NSURLSession sessionWithConfiguration:defultConfiguration delegate:nil delegateQueue:[NSOperationQueue currentQueue]];
NSURLSessionTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"请求完成!!!");
if (!error) {
NSString * result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
if (result) {
NSLog(@"解析成功:%@",result);
}else{
NSLog(@"解析失败!!!");
}
}else{
NSLog(@"请求失败 : %@",error.localizedDescription);
}
}];
[task resume];
}
AFNetworking
是自动保持cookie
的,我们不用去刻意处理它(获取与上传),除非你有一些需要。这里要讲的是我们手动管理cookie
,AFNetworking
中并没有专门为cookie
封装的代码,不过底层使用的是NSURLRequest
,所以我们可以获取到请求时服务器返回的cookie
,然后保存起来(删除和保存由我们自己管理),请求时候设置到request
到header
中即可。//默认是YES,cookie会被存储在共享的 NSHTTPCookieStorage 容器中
_manager.requestSerializer.HTTPShouldHandleCookies = NO;
// 获取所有数据报头信息
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)task.response;
NSDictionary *fields = [HTTPResponse allHeaderFields];
// 获取cookie
NSString *cookieString = [fields valueForKey:@"Set-Cookie"];
//保存到偏好设置中
[[NSUserDefaults standardUserDefaults] setObject:cookieString forKey:@"BoncUserDefaultsCookie"];
[[NSUserDefaults standardUserDefaults]synchronize];
NSString * cookie = [[NSUserDefaults standardUserDefaults] valueForKey:@"BoncUserDefaultsCookie"];
[self.manager.requestSerializer setValue:cookie forHTTPHeaderField:@"Cookie"];
ASIHTTPRequest
是自动保持cookie
的,如果我们所用cookie
请求数据,默认情况下我们不需要做其他任何操作。不同于AFNetworking
的是ASIHTTPRequest
对cookie
进行了封装,不管是获取还是设置都及其方便,这里是我们自己管理cookie
的做法。
设置不保持cookie
//默认是YES,cookie会被存储在共享的 NSHTTPCookieStorage 容器中,并且会自动被其他request重用。
request.useCookiePersistence = NO;
//清空session期间创建的所有cookie
//[ASIFormDataRequest setSessionCookies:nil];
//清除session期间产生的所有的cookie和缓存的授权数据。
// [ASIFormDataRequest clearSession];
//获取cookie
NSArray * cookies = [request responseCookies];
//获取responseHeader
// NSDictionary *headers = [request responseHeaders];
//保存cookie
NSData * cookiesData = [NSKeyedArchiver archivedDataWithRootObject:cookies];
[[NSUserDefaults standardUserDefaults]setValue:cookiesData forKey:@"BoncUserDefaultsCookies"];
[[NSUserDefaults standardUserDefaults]synchronize];
//依然要设置不保持cookie,否则传递cookie就不是我们自己保存到cookie
[request setUseCookiePersistence:NO];
//取出cookie
NSData * cookiesData = [[NSUserDefaults standardUserDefaults] valueForKey:@"BoncUserDefaultsCookies"];
NSArray * cookies = [NSKeyedUnarchiver unarchiveObjectWithData:cookiesData];
//设置cookie
[request setRequestCookies:cookies.mutableCopy];
AFNetworking
和ASIHTTPRequest
默认都是开启cookie
的,并且都是用[NSHTTPCookieStorage sharedHTTPCookieStorage]
管理cookie
的,所有二者可以通用,我们这里手动管理cookie
,可以自己控制cookie的生命周期,避免过期的情况。其实默认情况已经够我们正常使用了,这里需要注意点是默认都是开启cookie
的情况下,如在我们登录获取cookie
时候,一定要先清除cookie
,否则可能出现退出登录(不退出应用)换了个用户登录,登录后的数据还是上个用户的信息(未清除jsessionID
),虽然可以交给后台取处理(后台判断是不是登录接口,如果是就不取cookie
),但我们客户端也应该处理一下,避免该错误的发生。AFNetworking
请求登录接口时候清除cookie:
AFHTTPSessionManager * manager = [AFHTTPSessionManager manager];
[manager.requestSerializer setValue:nil forHTTPHeaderField:@"Cookie"];
//有时候这样设置并不好使,虽然也是在不保持cookie,但[NSHTTPCookieStorage sharedHTTPCookieStorage]还是保持了cookie,这样jsessionID并没有清除,所以为保险起见要清空[NSHTTPCookieStorage sharedHTTPCookieStorage]里面的cookies
- (void)clearCookies{
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray *_tmpArray = [NSArray arrayWithArray:[cookieStorage cookies]];
for (id obj in _tmpArray) {
[cookieStorage deleteCookie:obj];
}
}
ASIHTTPRequest
请求登录接口时候清除cookie:
[ASIFormDataRequest setSessionCookies:nil];
UIWebView
会将NSHttpRequest
的所有请求产生的cookie自动保存到NSHTTPCookieStorage
容器中,并且在同一个app
内多个UIWebView
之间共享,不需要我们做任何操作,在后续访问中会将 cookie
自动带到request
请求当中。
WKWebView
的cookie
问题在于 WKWebView
发起的请求不会自动带上存储于 NSHTTPCookieStorage
容器中的Cookie
,实践发现WKWebView
实例其实也会将cookie
存储于 NSHTTPCookieStorage
中,但存储时机有延迟,在iOS 8上,当页面跳转的时候,当前页面的 cookie
会写入NSHTTPCookieStorage
中,而在 iOS 10 上,JS 执行 document.cookie
或服务器 set-cookie
注入的 cookie
会很快同步到 NSHTTPCookieStorage
中,在执行 [WKWebView loadReques:]
前将 NSHTTPCookieStorage
中的内容复制到 WKHTTPCookieStore
中,以此来达到 WKWebView cookie
注入的目的。
Demo下载地址: OS开发之cookie研究demo
参考文章:
iOS平台下cookie的使用
ios AFNetworking—cookie(session)保持登陆会话状态
ASIHTTPRequest-Cookie的使用
UIWebView和WKWebView的cookie管理机制