[英]UIWebView to view self signed websites (No private api, not NSURLConnection) - is it possible?
有很多问题问这个问题:我可以获取UIWebView
来查看自签名的HTTPS网站吗?
答案总是涉及以下两个方面:
NSURLRequest
使用私有api调用: allowsAnyHTTPSCertificateForHost
NSURLConnection
代替,并且委托可以canAuthenticateAgainstProtectionSpace
等 对我来说,这些不会。
(1)-表示我无法成功提交到应用商店。
(2)-使用NSURLConnection意味着在加载初始HTML页面后必须从服务器获取的CSS,图像和其他内容不会加载。
有谁知道如何使用UIWebView来查看自签名的https网页,而这不涉及上述两种方法?
或者-如果实际上可以使用NSURLConnection
来渲染包含CSS,图像和其他所有内容的网页,那就太好了!
干杯,
伸展。
最后我明白了!
您可以执行以下操作:
照常使用UIWebView
发起您的请求。 然后-在webView:shouldStartLoadWithRequest
-我们回答NO ,而是使用相同的请求启动NSURLConnection。
使用NSURLConnection
,您可以与自签名服务器通信,因为我们能够通过UIWebView
不可用的额外委托方法来控制身份验证。 因此,使用connection:didReceiveAuthenticationChallenge
我们可以针对自签名服务器进行身份验证。
然后,在connection:didReceiveData
,我们取消NSURLConnection
请求,然后使用UIWebView
再次启动相同的请求-现在可以使用,因为我们已经通过服务器身份验证了:)
以下是相关的代码段。
注意:您将看到的实例变量具有以下类型:
UIWebView *_web
NSURLConnection *_urlConnection
NSURLRequest *_request
(我对_request
使用实例var,因为在我的情况下,它是具有大量登录详细信息的POST,但是如果需要,您可以更改为使用传入的请求作为方法的参数。)
#pragma mark - Webview delegate
// Note: This method is particularly important. As the server is using a self signed certificate,
// we cannot use just UIWebView - as it doesn't allow for using self-certs. Instead, we stop the
// request in this method below, create an NSURLConnection (which can allow self-certs via the delegate methods
// which UIWebView does not have), authenticate using NSURLConnection, then use another UIWebView to complete
// the loading and viewing of the page. See connection:didReceiveAuthenticationChallenge to see how this works.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
{
NSLog(@"Did start loading: %@ auth:%d", [[request URL] absoluteString], _authenticated);
if (!_authenticated) {
_authenticated = NO;
_urlConnection = [[NSURLConnection alloc] initWithRequest:_request delegate:self];
[_urlConnection start];
return NO;
}
return YES;
}
#pragma mark - NURLConnection delegate
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{
NSLog(@"WebController Got auth challange via NSURLConnection");
if ([challenge previousFailureCount] == 0)
{
_authenticated = YES;
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
} else
{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
{
NSLog(@"WebController received response via NSURLConnection");
// remake a webview call now that authentication has passed ok.
_authenticated = YES;
[_web loadRequest:_request];
// Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!)
[_urlConnection cancel];
}
// We use this method is to accept an untrusted site which unfortunately we need to do, as our PVM servers are self signed.
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
我希望这可以帮助其他人解决我遇到的同样的问题!
Stretch的答案似乎是一个不错的解决方法,但它使用了已弃用的API。 因此,我认为可能值得对代码进行升级。
对于此代码示例,我将例程添加到包含UIWebView的ViewController中。 我将UIViewController设置为UIWebViewDelegate和NSURLConnectionDataDelegate。 然后,我添加了2个数据成员:_Authenticated和_FailedRequest。 这样,代码如下所示:
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
BOOL result = _Authenticated;
if (!_Authenticated) {
_FailedRequest = request;
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
return result;
}
-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSURL* baseURL = [_FailedRequest URL];
if ([challenge.protectionSpace.host isEqualToString:baseURL.host]) {
NSLog(@"trusting connection to host %@", challenge.protectionSpace.host);
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
} else
NSLog(@"Not trusting connection to host %@", challenge.protectionSpace.host);
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)pResponse {
_Authenticated = YES;
[connection cancel];
[_WebView loadRequest:_FailedRequest];
}
加载视图且不重置时,我将_Authenticated设置为NO。 这似乎允许UIWebView向同一站点发出多个请求。 我没有尝试切换站点并尝试返回。 这可能会导致需要重置_Authenticated。 另外,如果要切换站点,则应保留_Authenticated的字典(每个主机一个条目),而不是BOOL。
这是灵丹妙药!
BOOL _Authenticated;
NSURLRequest *_FailedRequest;
#pragma UIWebViewDelegate
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
BOOL result = _Authenticated;
if (!_Authenticated) {
_FailedRequest = request;
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[urlConnection start];
}
return result;
}
#pragma NSURLConnectionDelegate
-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSURL* baseURL = [NSURL URLWithString:@"your url"];
if ([challenge.protectionSpace.host isEqualToString:baseURL.host]) {
NSLog(@"trusting connection to host %@", challenge.protectionSpace.host);
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
} else
NSLog(@"Not trusting connection to host %@", challenge.protectionSpace.host);
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)pResponse {
_Authenticated = YES;
[connection cancel];
[self.webView loadRequest:_FailedRequest];
}
- (void)viewDidLoad{
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:@"your url"];
NSURLRequest *requestURL = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:requestURL];
// Do any additional setup after loading the view.
}
如果您想使用自签名证书访问专用服务器以进行测试,则无需编写代码。 您可以手动在系统范围内导入证书。
为此,您需要使用移动浏览器下载服务器证书,然后提示输入。
在以下情况下可以使用:
如果您无权访问服务器证书,则可以使用以下方法从任何HTTPS服务器提取证书(至少在Linux / Mac上,Windows人员必须在某个地方下载OpenSSL二进制文件):
echo "" | openssl s_client -connect $server:$port -prexit 2>/dev/null | sed -n -e '/BEGIN\ CERTIFICATE/,/END\ CERTIFICATE/ p' >server.pem
请注意,根据OpenSSL版本的不同,证书可能会在文件中加倍,因此最好使用文本编辑器进行查看。 将文件放在网络上的某个位置或使用
python -m SimpleHTTPServer 8000
可以从移动浏览器访问它的快捷方式,网址为http:// $ your_device_ip:8000 / server.pem。
这是一个聪明的解决方法。 但是,一个更好的解决方案(尽管需要更多代码)将使用NSURLProtocol,如Apple的CustomHTTPProtocol示例代码所示。 从自述文件:
“ CustomHTTPProtocol显示了如何使用NSURLProtocol子类来拦截由高级子系统建立的NSURLConnections,否则该子系统不会公开其网络连接。在这种特定情况下,它将拦截由Web视图发出的HTTPS请求并覆盖服务器信任评估,允许您浏览默认情况下不受信任的证书的网站。”
检出完整的示例: https : //developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Introduction/Intro.html
这是与我兼容的2.0兼容版本。 我没有将此代码转换为使用NSURLSession
而不是NSURLConnection
,并且怀疑这样做会增加很多复杂性。
var authRequest : NSURLRequest? = nil
var authenticated = false
var trustedDomains = [:] // set up as necessary
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
authRequest = request
let urlConnection: NSURLConnection = NSURLConnection(request: request, delegate: self)!
urlConnection.start()
return false
}
else if isWebContent(request.URL!) { // write your method for this
return true
}
return processData(request) // write your method for this
}
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let challengeHost = challenge.protectionSpace.host
if let _ = trustedDomains[challengeHost] {
challenge.sender!.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!), forAuthenticationChallenge: challenge)
}
}
challenge.sender!.continueWithoutCredentialForAuthenticationChallenge(challenge)
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
authenticated = true
connection.cancel()
webview!.loadRequest(authRequest!)
}
这是swift 2.0的工作代码
var authRequest : NSURLRequest? = nil
var authenticated = false
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
authRequest = request
let urlConnection: NSURLConnection = NSURLConnection(request: request, delegate: self)!
urlConnection.start()
return false
}
return true
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
authenticated = true
connection.cancel()
webView!.loadRequest(authRequest!)
}
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
let host = "www.example.com"
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust &&
challenge.protectionSpace.host == host {
let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge)
} else {
challenge.sender!.performDefaultHandlingForAuthenticationChallenge!(challenge)
}
}
打造关@ spirographer的答案 ,我把东西在一起的雨燕2.0用例NSURLSession
。 但是,这仍然无法正常工作。 请参阅下文。
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
let result = _Authenticated
if !result {
let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
let task = session.dataTaskWithRequest(request) {
(data, response, error) -> Void in
if error == nil {
if (!self._Authenticated) {
self._Authenticated = true;
let pageData = NSString(data: data!, encoding: NSUTF8StringEncoding)
self.webView.loadHTMLString(pageData as! String, baseURL: request.URL!)
} else {
self.webView.loadRequest(request)
}
}
}
task.resume()
return false
}
return result
}
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))
}
我将返回初始的HTML响应,因此该页面呈现纯HTML,但是没有应用CSS样式(似乎获取CSS的请求被拒绝)。 我看到了很多这样的错误:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
似乎用webView.loadRequest
发出的任何请求都没有在会话中完成,这就是为什么连接被拒绝的原因。 我确实在Info.plist
设置了Allow Arbitrary Loads
。 令我困惑的是为什么NSURLConnection
可以工作(看似相同的想法),而不是NSURLSession
。
不推荐使用UIWebView
改用WKWebView
(可从iOS8获得)
设置webView.navigationDelegate = self
实行
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let trust = challenge.protectionSpace.serverTrust!
let exceptions = SecTrustCopyExceptions(trust)
SecTrustSetExceptions(trust, exceptions)
completionHandler(.useCredential, URLCredential(trust: trust))
}
}
并在plist中添加您要允许的域
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPSLoads</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>1.0</string>
<key>NSTemporaryExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
</dict>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.