iOS-Wiondows认证

三行代码让你通过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