I have implemented custom protocol with session delegate as below -
- (void)startLoading {
NSMutableURLRequest *mReq = [self.request mutableCopy];
NSURL *url = [[self request] URL];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
...
...
if(!_mySession) {
_mySession = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
}
.....
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler{
NSURLAuthenticationChallenge* challengeWrapper = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:[[CustomAuthChallengeWrappers alloc] initWithSessionCompletionHandler:completionHandler]];
if(self.client){
if([self.client respondsToSelector:#selector(URLProtocol:didReceiveAuthenticationChallenge:)]) {
debugLog("auth-challenge");
[self.client URLProtocol:self didReceiveAuthenticationChallenge:challengeWrapper];
}
}
}
If client uses NSURLConnection, it is working fine. If I use NSURLSession on client side, its forwarding the Auth challenge but not receiving back in custom protocol.
Challenge wrapper is implemented as per this link:
NSURLProtocol Challenge wrapper
Am I missing anything for NSURLSession?
Just to update it is working fine with iOS 10 and xcode 8 beta version. Looks like there was some problem in iOS 9.3 which has been fixed in this release.
Related
I have a task to do without ARC. Previously I didn't use it (started studying ios development recently). I have a class that represents http request, it conforms to NSURLSessionDownloadDelegate protocol. And also I have following code:
-(void)executeWithRelativeUrl:(NSString *)relativeUrl andSuccessBlock: (void(^) (NSData*))successBlock {
NSURL *url = [[NSURL alloc] initWithString:relativeUrl relativeToURL:self.baseUrl];
[self setSuccessBlock:successBlock];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
[downloadTask resume];
[request release];
[url release];
}
that creates url session and starts download task. I'm dealing with task results in following method:
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSData *data = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
self.successBlock(data);
});
}
Now the question is: do I need to release session, download task and location url in the end of the last method? Or it will be done for me? I'm asking this because I created it in the first method (except for url), and as I understand the one who is responsible for releasing the object is also me. Thanks!
The Golden Rule is very simple. Did you say alloc or copy or retain? No? Then you don't need to say release (and you must not do so).
(You need to release the url and the request for that reason, and you are doing so. So memory management is now complete.)
I am trying to use NSURLProtocol to handle authentication challenges for every NSURLSession connection . I used
[NSURLProtocol registerClass:[CustomHTTPSProtocol class]]
for NSURLConnection .But it is not working with NSURLSession so i need to setSessionConfiguration.protocolClasses=#[[CustomHTTPSProtocol class]];.
The problem is i use this URLProtocol class to only handle authentication challenges and i get received data's in the delegates of original class.
Something like this
OriginalClass
NSURLConnection
-(void)sendURL
{
[NSURLProtocol registerClass:[CustomHTTPSProtocol class]]; //done globally in didFinishLaunching
self.URL =[NSURL URLWithString:[[NSString stringWithFormat:#"https://www.google.co.in"]stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
NSMutableURLRequest *request=[[NSMutableURLRequest alloc]initWithURL:self.URL];
self.connection=[NSURLConnection connectionWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSlog(#"received data...%#",data);
}
NSURLSession
-(void)sendURL
{
self.URL =[NSURL URLWithString:[[NSString stringWithFormat:#"https://www.google.co.in"]stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
NSMutableURLRequest *request=[[NSMutableURLRequest alloc]initWithURL:self.URL];
sessionConfiguration =[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.protocolClasses=#[[CustomHTTPSProtocol class]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
NSURLSessionDataTask*task= [session dataTaskWithRequest:checkInInfoRequest];
[task resume];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
NSLog(#"data ...%# ",data); //handle data here
}
NSURLProtocol Class
NSURLConnection
-(void)startLoading
{
NSMutableURLRequest *newRequest = [self.request mutableCopy];
[NSURLProtocol setProperty:#YES forKey:CustomHTTPSProtocolHandledKey inRequest:newRequest];
self.connection=[NSURLConnection connectionWithRequest:newRequest delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
else
{
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
}
NSURLSession
- (void) startLoading {
NSMutableURLRequest *newRequest = [self.request mutableCopy];
[NSURLProtocol setProperty:#YES forKey:CustomHTTPSProtocolHandledKey inRequest:newRequest];
NSURLSession*session=[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
self.sessionTask=[session dataTaskWithRequest:newRequest];
[self.sessionTask resume];
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
Using the above logic i was able to handle authentication for all request globally and handling response individually. But with NSURLSession if i use NSURLProtocol, authentication is done in Protocol class but i am not able to receive data, since my original class delegates are not called.
Somebody help me out.
When you implement making the request yourself (or resend it as you did), your protocol becomes the challenge sender. You are responsible for creating an NSURLAuthenticationChallenge object, setting yourself as the sender, providing all the other bits from the existing challenge, and sending it using the NSURLProtocolClient class.
Take a look at Apple's CustomHTTPProtocol Sample Code project for examples and further information.
My application uses NSURLConnection to communicate with server. We use https for communication. In order to handle authentication from all request in one place i used NSURLProtocol and handled authentication in delegates in that class. Now I have decided to use NSURLSession instead of NSURLConnection. I am trying do get NSURLProtocol working with NSURLSession
I created a task and used NSURLProtocol by
NSMutableURLRequest *sampleRequest = [[NSMutableURLRequest alloc]initWithURL:someURL];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.protocolClasses = #[[CustomHTTPSProtocol class]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:checkInInfoRequest];
[task resume];
CustomHTTPSProtocol which is my NSURLProtocol class looks like this
static NSString * const CustomHTTPSProtocolHandledKey = #"CustomHTTPSProtocolHandledKey";
#interface CustomHTTPSProtocol () <NSURLSessionDataDelegate,NSURLSessionTaskDelegate,NSURLSessionDelegate>
#property (nonatomic, strong) NSURLSessionDataTask *connection;
#property (nonatomic, strong) NSMutableData *mutableData;
#end
#implementation CustomHTTPSProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
if ([NSURLProtocol propertyForKey:CustomHTTPSProtocolHandledKey inRequest:request]) {
return NO;
}
return [[[[request URL]scheme]lowercaseString]isEqualToString:#"https"];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
- (void) startLoading {
NSMutableURLRequest *newRequest = [self.request mutableCopy];
[NSURLProtocol setProperty:#YES forKey:CustomHTTPSProtocolHandledKey inRequest:newRequest];
NSURLSession*session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
self.connection = [session dataTaskWithRequest:newRequest];
[self.connection resume];
self.mutableData = [[NSMutableData alloc] init];
}
- (void) stopLoading {
[self.connection cancel];
self.mutableData = nil;
}
-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSLog(#"challenge..%#",challenge.protectionSpace.authenticationMethod);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
else {
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
NSLog(#"data ...%# ",data); //handle data here
[self.mutableData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (!error) {
[self.client URLProtocolDidFinishLoading:self];
}
else {
NSLog(#"error ...%# ",error);
[self.client URLProtocol:self didFailWithError:error];
}
}
#end
Start loading is called and also authentication challenge is done but stop loading is called immediately after that.
Error code -999 "Cancelled" is returned after some time. didReceiveData is not called.
Note:NSURLProtocol and the Authentication Process worked fine with NSURLConnection.
What am I missing ?? My Questions are
Registering [NSURLProtocol registerClass:[CustomHTTPSProtocol class]]; worked fine with NSURLConnection but how to Resister for NSURLProtocol Globally with NSURLSession ?.
Why are the requests getting failed in NSURLProtocol(same URL and logic worked withURLConnection) with URLSession and how to get NSURLProtocol working with URLSession ?.
Kindly help me and let me know if you want any more details.
Registering a custom NSURLProtocol with NSUrlsession is same as the way you do with NSURLConnection.
NSURLSession Api's (NSURLSessionDataTask and other task classes) when used with custom NSUrlProtocol fails to handle HTTP Authentication Challenges properly.
This was working as expected on iOS 7.x and is broken in iOS 8.x.Raised a apple radar and my radar was closed saying it was a duplicate of another radar and I Still see the original radar is still open. So I believe this issue is not yet fixed by apple as of today.
Hope I am not too late with the answer :)
I know this is old but the problem you are having is in your didReceiveChallenge method. You are ending the method by calling
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
or
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
What you need to be doing instead is using the completion handler to send your results. It would look like this:
completionHandler(.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust)
or
completionHandler(.PerformDefaultHandling, nil)
These are in Swift 2.0 but should translate nicely to Obj-C.
My vague recollection is that NSURLSession automatically uses any globally registered protocols. So you shouldn't need to register it again, but it also shouldn't hurt to do so.
I wonder if the URL request is getting copied, in which case your custom tagging might not work, and you might be seeing infinite recursion until it hits some internal limit. Have you tried setting a request header instead?
I have a server with a self-signed SSL certificate installed. However, once I call the following method it doesn't get any response. Once I change the URL back to http, it works.
- (void)getAccountInfoWithCompletion:(void (^)(NSDictionary *json_response, NSError *error))completion
{
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:
[NSString stringWithFormat:#"%#/api/account/%d/get_info", BASE_HOST_URL_IP, [self getUserID]]
]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error)
{
if (completion)
{
//completion(#"error", error);
}
} else {
NSString *response_string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *json_object = [NSJSONSerialization JSONObjectWithData:[response_string dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
if (completion)
{
completion(json_object, error);
}
}
}];
}
My reason for the delegate is so I can use the self-signed certificate in my app. The following tutorial is what I was using, but then I realized I couldn't use the delegate with the completionHandler method. I need to keep the completionHandler method though.
http://www.cocoanetics.com/2010/12/nsurlconnection-with-self-signed-certificates/
What could I do in order to receive a response from the SSL site?
In the case you describe, you (pretty much) have to use a delegate.
What's happening here is that sendAsynchronousRequest:queue:completion: uses the default behavior of the URL loading system. The URL loading system sees your self signed certificate, can't verify it, so it can't trust it - and won't connect. You should see the NSError passed in to the completion handler populated with information about the problem.
This is all described in depth in Technote 2232: HTTPS Server Trust Evaluation
To allow your self signed certificate, you can't use sendAsynchronousRequest:queue:completion: unless you have a way to make your self-signed certificate trusted and stored in the keychain - on iOS this is only practical in managed devices. For testing, and ONLY for testing, you can use a private Apple API to alter the default trust behavior.
For production code, you must implement a NSURLConnectionDelegate that handles evaluating the server provided credentials and allows your self-signed certificate. This is also described (at a high level) in Technote 2232. If you do not implement this correctly you may create a security vulnerability in your app - and that would be bad, mmmmmk?
I would not suggest following the guidance of the Cocoanetics post you reference. The material is outdated and of questionable quality. Refer to the documentation for NSURLConnectionDelegate and the mentioned Technote 2232 instead. If you would like more information on transport level security for mobile applications in general, there are plenty of resources available.
If you STILL want to use a self signed certificate, you can implement SSL public key pinning to match the remote (self signed) public key against a known local value stored inside your application. This is much better than attempting to match just the hostname. Some example code to get you started is here
ViewController.h:
#interface ViewController : UIViewController <NSURLSessionDelegate>
#end
ViewController.m:
- (void)getAccountInfoWithCompletion:(void (^)(NSDictionary *json_response, NSError *error))completion
{
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:
[NSString stringWithFormat:#"%#/api/account/%d/get_info", BASE_HOST_URL_IP, [self getUserID]]
]];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration ephemeralSessionConfiguration];
defaultConfigObject.requestCachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURLSessionDataTask *dataTask = [defaultSession dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (error == nil && data != nil)
{
NSString *response_string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *json_object = [NSJSONSerialization JSONObjectWithData:[response_string dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
if (completion)
{
completion(json_object, error);
}
}
}];
[dataTask resume];
}
The new beautiful delegate method which lets us replace NSURLConnection's sendAsynchronousRequest method (which couldn't handle SSL)
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
NSString *host = challenge.protectionSpace.host;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
if ([host rangeOfString:#"yourHost.net"].location != NSNotFound)
{
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
else
{
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,nil);
}
}
}
In iPad app, I need to connect to a server and download files that's using a self-signed SSL certificate with NSURLSession object in background mode:
static NSString *const URL = #"https://...";
- (void)testBackgroundDownloadTask {
if (self.downloadTask) {
return self;
}
self.session = [self backgroundSession];
NSURL *downloadURL = [NSURL URLWithString:URL];
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
self.downloadTask = [self.session downloadTaskWithRequest:request];
[self.downloadTask resume];
}
- (NSURLSession *)backgroundSession{
static NSURLSession *sess = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:URL];
// Default configuration: working perfectly
//NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.sessionSendsLaunchEvents = YES;
//configuration.TLSMinimumSupportedProtocol = kSSLProtocolAll;
configuration.networkServiceType = NSURLNetworkServiceTypeBackground;
sess = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
});
return sess;
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;
NSString *authMethod = protectionSpace.authenticationMethod;
if ([authMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
// obtain challenge, it working with NSURLSession in default session config
[self.challengeHandler handleChallenge:challenge onCompletion:^(id obj) {
NSURLCredential *c = (NSURLCredential *)obj;
if (c) {
[challenge.sender useCredential:c forAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential, c);
}
else {
[challenge.sender cancelAuthenticationChallenge:challenge];
}
}];
}
else if ([authMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
}
If I use defaultSessionConfiguration, my app after call didReceiveChallenge method perform successfully download file (call NSURLSesionDownloadDelegate methods
– URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
– URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:
– URLSession:downloadTask:didFinishDownloadingToURL:
But I use backgroundSessionConfiguration, after call didReceiveChallenge other delegate’s methods not called (file not downloading and didCompleteWithError not called)
Any ideas on how to resolve this problem?
Are you testing this in the background?
On your NSURLSessionDownloadDelegate, you should see what you get when you implement
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
NSLog(#"%s %# %#", __PRETTY_FUNCTION__, task.response, error);
}
If any of that stuff in –URLSession:didReceiveChallenge:completionHandler: is async, then you need to surround it with a UIApplication/UIBackgroundTaskIdentifier {begin,end}BackgroundTask..., or iOS will kill your background process when the stack pops.