これを尋ねる質問がたくさんあります:自己署名されたHTTPS Webサイトを表示するためにUIWebView
を取得できますか?
そして、答えには常に次のいずれかが含まれます。
NSURLRequest
:allowsAnyHTTPSCertificateForHost
にプライベートAPI呼び出しを使用しますNSURLConnection
とデリゲートcanAuthenticateAgainstProtectionSpace
などを使用してください私にとっては、これらはしません。
(1)-アプリストアに正常に送信できないことを意味します。
(2)-NSURLConnectionを使用すると、最初のHTMLページを受信した後にサーバーから取得する必要があるCSS、画像、およびその他のものがロードされません。
UIWebViewを使用して自己署名https Webページを表示する方法を知っている人はいますか?上記の2つの方法は関係ありませんか?
または-NSURLConnection
を使用して、CSS、画像、その他すべてを備えたWebページを実際にレンダリングできる場合、それは素晴らしいことです!
乾杯、
ストレッチ。
ついにできた!
あなたができることはこれです:
通常どおりUIWebView
を使用してリクエストを開始します。次に-webView:shouldStartLoadWithRequest
で[〜#〜] no [〜#〜]と応答し、代わりに同じリクエストでNSURLConnectionを開始します。
NSURLConnection
を使用すると、UIWebView
では利用できない追加のデリゲートメソッドを介して認証を制御できるため、自己署名サーバーと通信できます。したがって、connection:didReceiveAuthenticationChallenge
を使用して、自己署名サーバーに対して認証できます。
次に、connection:didReceiveData
で、NSURLConnection
リクエストをキャンセルし、UIWebView
を使用して同じリクエストを再度開始します。これは、サーバー認証を既に通過しているため、現在は機能します。 )
以下に関連するコードスニペットを示します。
注:表示されるインスタンス変数は次のタイプです。UIWebView *_web
NSURLConnection *_urlConnection
NSURLRequest *_request
(私の場合のように_request
にインスタンス変数を使用します。これは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をリセットする必要が生じる場合があります。また、サイトを切り替える場合は、BOOLではなく_Authenticatedの辞書(ホストごとに1つのエントリ)を保持する必要があります。
これが万能薬です!
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サーバーから抽出するために following method にフォールバックできます(少なくとも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のバージョンによっては、証明書がファイル内で2倍になる場合があるため、テキストエディターで確認することをお勧めします。ネットワーク上のどこかにファイルを置くか、
python -m SimpleHTTPServer 8000
http:// $ your_device_ip:8000/server.pemでモバイルサファリからアクセスするショートカット。
これは賢い回避策です。ただし、おそらくより優れた(よりコード集約的な)ソリューションは、AppleのCustomHTTPProtocolサンプルコードで示されているようにNSURLProtocolを使用することです。 READMEから:
「CustomHTTPProtocolは、NSURLProtocolサブクラスを使用して、ネットワーク接続を公開しない高レベルのサブシステムによって作成されたNSURLConnectionsをインターセプトする方法を示します。この特定のケースでは、Webビューによって作成されたHTTPS要求をインターセプトし、サーバーの信頼評価をオーバーライドします。デフォルトで証明書が信頼されていないサイトを閲覧できます。」
完全な例を確認してください: https://developer.Apple.com/library/ios/samplecode/CustomHTTPProtocol/Introduction/Intro.html
これはSwift 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's answer から構築するために、Swift 2.0ユースケースNSURLSession
を使用して何かをまとめました。しかし、これはまだ[〜#〜] not [〜#〜]は動作します。以下を参照してください。
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
で行われたリクエストはセッション内ではなく行われているように見えるため、接続は拒否されます。 Allow Arbitrary Loads
にInfo.plist
を設定しています。私を混乱させているのは、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>