三行代码让你通过UIWebview中遇到的NTLM验证
Windows认证分为NTLM
认证 和Kerberos v5
身份认证,这里只列出NTLM认证的情况。在移动开发中细分为两种情况(iOS为例)
访问的API是带NTLM认证+From身份认证
其中NTLM认证只是作为一种加强方式(只是微软环境才有的),或者受限于服务端的其他环境需要。那么在移动端该层认证有点多余,并且会影响效率。因为每次访问该接口都是先返回401质询后,提取出scheme,若为NTLM再设置其认证信息,如设置其domain信息等。
如访问xx移动登录接口,就一个典型的带了windows认证后又要传Form参数的情况
http://xxx.xxx.xx:8600/MobileService/MobileHandler.ashx?action=Login
Form参数列表这里就不一一列出
如何获取需要NTLM api的数据?若使用ASIHttp的,只需要设置 request的username和password即可。AFHttpNetwork同理,只是设置credentials的时候复杂些。但是要用原生的NSURLConnection 或者NSURLSession 请参见下文中访问带NTLM验证的网页。从网络抓包结果来看:访问一次接口要先失败一次第二次传身份信息才成功,某种程度上导致效率较低。
访问的资源是html
即html需要先过了NTLM验证才能访问,如访问路由器的页面192.168.1.1,会默认弹出一个认证框出来。如果是我们自己开发或者集成带有windows认证的页面,就无法自动弹窗出来,因为弹窗属于窗口级别、在我们开发的程序中不能越级访问系统资源,需要我们手动实现起认证功能。这里用iOS原生的sdk方法说明。
Step 1,
1 2 3 4 5
| NSURLRequest *req =[NSURLRequest requestWithURL:reqUrl]; NSURLConnection *connect =[NSURLConnection connectionWithRequest:req delegate:self]; [connect start];
|
构建一个NSURLRequest请求对象,只需要一个NSURL地址即可,这里reqUrl可以写为
[NSURL UrlWithString:@”http://需要过windows认证的页面地址”]
Step 2,
以NSURLRequest的实例对象构建NSURLConnection实例对象,并设置Connection的委托方法,可以理解为回调方法,当请求发送到服务端后,服务端返回数据后就会进入对应的回调方法,返回该连接是否需要认证、或者其他然后再进行处理。
进入NSURLConnection的回调方法后,判断其认证方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| //处理身份认证(void)connection:(NSURLConnection *)connectiondidReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if ([challenge previousFailureCount] == 0) { //若是windows认证方式,iOS的sdk会自动进入该回调方法, //在没有尝试认证失败的情况下,设置Credential的参数 //USERNAME //PASSWORD [[challenge sender]useCredential:[NSURLCredential credentialWithUser:@"USERNAME"password:@"PASSWORD"persistence:NSURLCredentialPersistencePermanent]forAuthenticationChallenge:challenge]; NSLog(@"...1"); }else { 若没有认证,则取消AuthenticaionChllenge的操作 [[challenge sender]cancelAuthenticationChallenge:challenge]; } } //处理访问的网页,在改方法里面再用webview载入request请求后,就可以实现了访问带有windows认证的网页。 -(void)connection:(NSURLConnection *)connectiondidReceiveResponse:(NSURLResponse *)response; { NSLog(@"received response viansurlconnection"); /** THIS IS WHERE YOU SET MAKE THE NEWREQUEST TO UIWebView, which will use the new saved auth info **/ #pragma mark -真实访问的url地址 NSURLRequest *urlRequest = [NSURLRequestrequestWithURL:[NSURL URLWithString:@"http://192.168.90.130/_layouts/ReportServer/RSViewerPage.aspx?rv%3aRelativeReportUrl=%2fReportLib%2fReports%2f%25E8%25BF%2590%25E8%2590%25A5-%25E5%25AE%25A2%25E8%25BF%2590%25E9%2587%258F.rdl"]]; [_webView loadRequest:urlRequest]; }
|
以上的代码适用于iOS7或者iOS8,但是在iOS9以上,苹果废弃了NSURLConnection,那么我们需要做的就是将NSURLConnection的方式迁移到NSURLSession的回调方法即可。
show me the code
构建NSURLSession请求,设置委托回调
1 2 3 4 5 6
| NSMutableURLRequest *webReq = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx.com:8000/todolist.aspx"]]; NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *conn =[NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]]; NSURLSessionDataTask *task = [conn dataTaskWithRequest:webReq]; [task resume];
|
在回调鉴权中填入相关信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler { NSString *authMethod = [[challenge protectionSpace] authenticationMethod]; //下面的鉴权没有判断是哪种鉴权方法,精细点可以只针对哪种认证做处理 if ([challenge previousFailureCount] == 0) { _authed = YES; /* SET YOUR credentials, i'm just hard coding them in, tweak as necessary */ NSURLCredential *cred = [NSURLCredential credentialWithUser:@"USERNAME" password:@"PASSWORD" persistence:NSURLCredentialPersistenceNone]; [[challenge sender] useCredential:cred forAuthenticationChallenge:challenge]; completionHandler(NSURLSessionAuthChallengeUseCredential, cred); NSLog(@"Finished Challenge"); } else { [[challenge sender] cancelAuthenticationChallenge:challenge]; } }
|
webview载入request
1 2 3 4 5 6 7
| - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://xxxx:8000/mERP/MobileWorkflow/todolist.aspx?usercode=USERNAME&password=PWD"]]; [_myWebView loadRequest:urlRequest]; }
|
写在最后
以上设置解决api访问级别的应该没有问题,但是测试在iOS9.3+webview载入request依然会有问题。可以构建一个NSURLComponets来解决这个问题。鉴权那套都不需要了
1 2 3 4
| NSURLComponents *componet = [NSURLComponents componentsWithString:@"http://xxxx.xxx.com:8000/mERP/MobileWorkflow/todolist.aspx?usercode=USERNAME&password=PASSWORD"]; componet.user = @"USERNAME"; componet.password = @"PASSWORD"; [_myWebView loadRequest:[NSURLRequest requestWithURL:componet.URL]];
|
就是这么任性
总结:
不管是利用三方框架还是sdk的自带方法,用了windows认证的情况都是存在两次连接的情况的。至于在pc中是否也是两次连接我这里没有做详细研究。
补充:
在Android开发中实现了访问NTLM认证网页或者带windows认证接口的问题,现在在xx地铁中安卓端的代码就是采用的我在文中列出的解决办法。
至于访问地铁报表页面或者访问公文文档库页面,显示自动匹配的问题我们是通过Client的UA字符串的办法来解决的,即让安卓客户端欺骗Sharepoint服务器它是iOS设备,并且这里UA字符串是我测试了iOS6,iOS7的各个硬件设备提取出来的。
Mozilla/5.0 (iPhone; CPUiPhone OS 6_1_3 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko)Version/6.0 Mobile/10B329 Safari/8536.25