PAYATHON 2026

UIWebView 无法打开 HTTPS 安全 URL

支付阿杰

结论

UIWebView 不会因为 URL 使用 HTTPS 就拒绝加载。更常见的原因是支付网关的 TLS/SSL 配置未通过 iOS 安全校验,比如证书链不完整、证书过期、域名不匹配,或者使用了旧版 TLS 和不兼容的加密套件。

Safari 能打开同一个 URL,不代表应用内的 WebView 也能正常加载。Safari 与应用使用的网络栈、缓存、Cookie、客户端证书及认证流程可能不同。排查时应先取得实际错误码,再根据错误修复服务器配置或应用的访问流程。

先记录真实错误

为 UIWebView 设置代理,记录加载失败信息:

@interface ViewController () <UIWebViewDelegate>
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.webView.delegate = self;

    NSString *urlString =
        @"https://xxxseal.xxxxbank.com/mpi/Ssl.jsp?transactionId=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];
}

- (void)webView:(UIWebView *)webView
didFailLoadWithError:(NSError *)error {
    NSLog(@"WebView load failed");
    NSLog(@"domain: %@", error.domain);
    NSLog(@"code: %ld", (long)error.code);
    NSLog(@"description: %@", error.localizedDescription);
    NSLog(@"userInfo: %@", error.userInfo);
}

@end

不要看到页面空白就直接认定是证书问题。支付页面也可能因为重定向失败、Cookie 缺失、JavaScript 异常,或者服务器拒绝 WebView 的 User-Agent 而无法显示。

如果错误是 NSURLErrorCancelled,错误码通常为 -999,往往只是重定向或新请求替换旧请求时产生的取消通知,不一定表示加载失败。还要继续检查后续错误和服务器响应。

常见原因

1. 服务器没有返回完整证书链

支付网关可能只安装了站点证书,没有正确配置中间证书。桌面浏览器或 Safari 有时能通过缓存或其他机制补齐证书链,应用内网络请求则未必可以。

服务器应返回:

  • 站点证书;
  • 所需的中间 CA 证书;
  • 顺序正确的证书链。

服务器通常不需要发送根 CA 证书。这个问题应由银行或支付网关的运维方修复,客户端不应靠跳过证书校验来掩盖配置错误。

2. 证书无法通过系统信任校验

需要检查:

  • 证书是否已过期或尚未生效;
  • 证书中的域名是否与请求域名一致;
  • 证书是否由 iOS 信任的 CA 签发;
  • 设备的日期和时间是否正确;
  • 是否使用了自签名证书或企业内部 CA;
  • 重定向涉及的其他 HTTPS 域名是否也有有效证书。

支付流程通常会跳转多个域名。入口 URL 的证书正常,不代表后续认证页、3-D Secure 页面和回调地址的证书也没有问题。

3. TLS 协议或加密套件过旧

旧支付系统可能只支持已经淘汰的 SSL/TLS 版本或旧加密算法。较老的 iOS 设备也可能不支持服务器要求的新协议和算法。

判断时需要结合以下信息:

  • 出现问题的 iOS 版本;
  • 服务器支持的 TLS 版本;
  • 服务器提供的加密套件;
  • 服务器是否要求 SNI;
  • TLS 握手是否失败。

这类兼容问题通常需要在支付网关侧升级 TLS 配置。不要为了兼容旧服务器而降低支付应用的整体安全标准。

4. App Transport Security 限制

如果应用运行在已经引入 ATS 的 iOS 版本上,而服务器的 TLS 配置不符合 ATS 要求,请求可能会被阻止。不过,是否由 ATS 引起,仍要根据错误日志和实际系统版本判断,不能只看 URL 是否使用 HTTPS。

临时诊断时,可以为指定域名设置例外,但不建议对支付网关关闭全局安全限制:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>xxxxbank.com</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

是否还需要与 TLS 版本有关的例外键,要根据目标 iOS SDK、部署版本和服务器配置确定。ATS 例外无法修复证书过期、域名不匹配或证书不受信任等问题,也不适合作为支付环境中的长期方案。

5. 支付页面依赖 Cookie、POST 数据或请求头

有些支付网关不能通过复制最终 URL 直接进入。它们可能要求:

  • 由商户服务器发起 POST 请求;
  • 携带特定 Cookie 或 Session;
  • 携带 Referer、User-Agent 等请求头;
  • 使用一次性且尚未过期的 transactionId;
  • 按规定顺序完成重定向;
  • 通过自定义 URL Scheme 返回商户 App。

如果 Safari 是从完整支付流程中打开页面,而 UIWebView 只是单独加载最终 URL,那么两者实际上不是同一次请求。此时应严格按照支付网关提供的 iOS 接入文档构造请求,不能只加载地址字符串。

POST 表单请求可以按网关要求构造,例如:

NSURL *url = [NSURL URLWithString:@"https://xxxseal.xxxxbank.com/mpi/Ssl.jsp"];

NSString *bodyString =
    @"transactionId=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
[request setValue:@"application/x-www-form-urlencoded"
forHTTPHeaderField:@"Content-Type"];

[self.webView loadRequest:request];

参数名称、编码方式、签名和 HTTP 方法必须以支付网关的实际接口规范为准。

推荐解决步骤

  1. 在 didFailLoadWithError: 中记录完整的 NSError。
  2. 确认失败发生在入口 URL,还是某个重定向地址。
  3. 在相同设备和网络环境下检查证书有效期、域名和完整证书链。
  4. 请网关维护方检查 TLS 协议、加密套件及中间证书配置。
  5. 核对支付接口是否要求 POST、Cookie、Session 或特定请求头。
  6. 如果日志明确指向 ATS,再添加范围最小的域名例外进行验证。
  7. 生产环境应优先修复服务器配置,不要绕过 TLS 信任检查。

新项目应改用 WKWebView

UIWebView 已不适合用于新项目。可以改用 WKWebView,它对导航、认证挑战和错误处理提供了更完整的支持:

#import <WebKit/WebKit.h>

@interface ViewController () <WKNavigationDelegate>
@property (nonatomic, strong) WKWebView *webView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
    self.webView.navigationDelegate = self;
    [self.view addSubview:self.webView];

    NSURL *url = [NSURL URLWithString:
        @"https://xxxseal.xxxxbank.com/mpi/Ssl.jsp?transactionId=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"];

    [self.webView loadRequest:[NSURLRequest requestWithURL:url]];
}

- (void)webView:(WKWebView *)webView
didFailProvisionalNavigation:(WKNavigation *)navigation
      withError:(NSError *)error {
    NSLog(@"Provisional navigation failed: %@", error);
}

- (void)webView:(WKWebView *)webView
didFailNavigation:(WKNavigation *)navigation
      withError:(NSError *)error {
    NSLog(@"Navigation failed: %@", error);
}

@end

即使改用 WKWebView,无效证书和错误的 TLS 配置仍需修复。更换 WebView 组件不能绕过 HTTPS 校验。

注意事项

不要在正式支付环境中接受所有服务器证书、忽略主机名校验,或无条件执行 NSURLSessionAuthChallengeUseCredential。这样会使应用容易受到中间人攻击,还可能导致交易信息泄露。

如果支付网关要求客户端证书、HTTP Basic/Digest 认证或特殊的 3-D Secure 交互,需要按照其接入规范处理认证挑战。仅凭当前 URL 无法判断是否存在这些要求,应以实际错误码、网络日志和网关文档为准。

备注:内容仅供参考。