iOS开发之cookie
原创 面壁者LOGIC 发布于2018-12-19 19:01:42
本文链接:https://blog.csdn.net/Bolted_snail/article/details/85105524
最近公司项目安全检查检测到缓存中的用户名和密码,虽然已经加密,但还是不安全;所以公司要求用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<NSHTTPCookie *> *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<NSHTTPCookie *> *)cookiesForURL:(NSURL *)URL;
NSHTTPCookie里面有个properties的属性,其组成如下:
<__NSArrayM0x283bec540>(<NSHTTPCookieversion:0name:JSESSIONIDvalue:A0787301B667F5E1C7B5BABAAE58B104expiresDate:'(null)'created:'2018-12-27 02:39:06 +0000'sessionOnly:TRUEdomain:192.168.1.121partition:nonesameSite:nonepath:/innerisSecure:FALSEisHTTPOnly:YES path:"/inner"isSecure:FALSE isHTTPOnly:YES>,<NSHTTPCookieversion:0name:tokenvalue:1545878346436_111_cd90cdd1-9938-4e18-8ccd-79ad54d5527cexpiresDate:'2019-01-26 02:39:06 +0000'created:'2018-12-27 02:39:06 +0000'sessionOnly:FALSEdomain:192.168.1.121partition:nonesameSite:nonepath:/innerisSecure: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获取cookieNSArray*array=[NSHTTPCookie cookiesWithResponseHeaderFields:httpresponse.allHeaderFields forURLresponse.URL];for(NSHTTPCookie*cookieinarray){//保存到sharedHTTPCookieStorage中[[NSHTTPCookieStorage sharedHTTPCookieStorage]setCookie:cookie];}//方式2:NSHTTPURLResponse获取NSHTTPURLResponse*httpresponse=(NSHTTPURLResponse*)response;NSDictionary*dic=httpresponse.allHeaderFields;//获取cookie字符串NSString*cookiesStr=[dic valueForKey:@"Set-Cookie"];//方式3:NSHTTPCookieStorage获取cookieNSArray*cookies=[[NSHTTPCookieStorage sharedHTTPCookieStorage]cookiesForURL:response.URL];
多个cookie的格式通常为:cookie1=value1; cookie2=value2; cookie3=value3; 这里特别要注意,多个cookie之间用分号+空格分隔开,不是&也不是单纯的空格。我所了解到的iOS设置Cookie的方法有两种。
设置cookie方式1: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。
1.使用系统管理cookie的类NSHTTPCookieStorage管理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中即可。
设置不保持cookie
//默认是YES,cookie会被存储在共享的 NSHTTPCookieStorage 容器中
_manager.requestSerializer.HTTPShouldHandleCookies = NO;
获取cookie,并保存起来
// 获取所有数据报头信息
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];
设置cookie
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:
//获取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就不是我们自己保存到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和WKWebView的cookie管理机制
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