我们知道代码发起 URLSession 请求时,会有个-URLSession:didReceiveChallenge:completionHandler:
代理函数自定义认证逻辑;在使用 WebKit 时,也有个-webView:didReceiveAuthenticationChallenge:completionHandler:
代理函数实现同样功能。
不难推测,WebKit 底层是使用 URLSession 时把认证函数转接到外部供开发者使用的。不过在实际的开发过程中却发现了不回调的情况,不得不挖一下内部逻辑。
Network 进程的漏斗
WebKit 是在 NetworkSessionCocoa 里面做网络请求回执处理的,跟着 Authentication Challenge 代理回调逻辑发现:
ini 代码解读复制代码 // step1
// Proxy authentication is handled by CFNetwork internally. We can get here if the user cancels
// CFNetwork authentication dialog, and we shouldn't ask the client to display another one in that case.
if (challenge.protectionSpace.isProxy
&& sessionCocoa->proxyConfigs().isEmpty()
&& !sessionCocoa->preventsSystemHTTPProxyAuthentication())
return completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
// step2
NegotiatedLegacyTLS negotiatedLegacyTLS = NegotiatedLegacyTLS::No;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSURLSessionTaskTransactionMetrics *metrics = task._incompleteTaskMetrics.transactionMetrics.lastObject;
auto tlsVersion = (tls_protocol_version_t)metrics.negotiatedTLSProtocolVersion.unsignedShortValue;
if (tlsVersion == tls_protocol_version_TLSv10 || tlsVersion == tls_protocol_version_TLSv11)
negotiatedLegacyTLS = NegotiatedLegacyTLS::Yes;
if (negotiatedLegacyTLS == NegotiatedLegacyTLS::No && [task respondsToSelector:@selector(_TLSNegotiatedProtocolVersion)]) {
SSLProtocol tlsVersion = [task _TLSNegotiatedProtocolVersion];
if (tlsVersion == kTLSProtocol11 || tlsVersion == kTLSProtocol1)
negotiatedLegacyTLS = NegotiatedLegacyTLS::Yes;
}
// step3
if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes && task._preconnect)
return completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
…
分析相关上下文解读核心含义如下。
step1
若是代理认证且用户主动拒绝了认证弹窗,则默认处理,意味着不会回调到 WebKit 外部代理供开发者拦截。
step2
首先从 URLSession 里面的 metrics 性能数据里面读取 TLS 的版本,然后从 NSURLSessionTask 里面去 TLS 版本。 最终,若 TLS 版本是 1.0 和 1.1 则认为是 legacy TLS,估计是由于历史遗留问题不得不做特殊处理。
这里用到了几个枚举类型,tls_protocol_version_t 的值其实和 swift 里面的 tls_protocol_version_t 枚举值是一样的,kTLSProtocol1 的值也就是 Secure 系统库里面的枚举。
step3
若是 legacy TLS 且支持了 preconnect 则默认处理。preconnect 就是在网络进程还没有去加载网页的资源时,提前去与 host 对应的服务进行预连接,这个能力可能是开启的。
所以有漏斗1:若 TLS 版本低了可能永远无法走到自定义认证函数。
后续还会存在一些是否合并 challenge 的判定,如果是 serverTrust 则会正常流转。
php 代码解读复制代码static bool canCoalesceChallenge(const WebCore::AuthenticationChallenge& challenge) {
// Do not coalesce server trust evaluation requests because ProtectionSpace comparison does not evaluate server trust (e.g. certificate).
return challenge.protectionSpace().authenticationScheme() != ProtectionSpace::AuthenticationScheme::ServerTrustEvaluationRequested;
}
所以有漏斗2:若 TLS 认证被网络库错误的判定为非 serverTrust。
网络库判定 serverTrust 逻辑
把 Swift 的 URLSession 源码搞下来看了下,发现竟然不支持 NSURLAuthenticationMethodServerTrust 类型,跟 OC 的实现估计是大不相同,所以这部分还不太好查。当然这也符合预期,就像 Swift URLCache 的实现也非常拙劣,跟 OC 完全不一样。
UI 进程的漏斗
目标逻辑:
scss 代码解读复制代码void WebPageProxy::didReceiveAuthenticationChallengeProxy(…) {
if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes) {
m_navigationClient->shouldAllowLegacyTLS(…) {
if (shouldAllowLegacyTLS)
m_navigationClient->didReceiveAuthenticationChallenge(*this, authenticationChallenge.get());
else
authenticationChallenge->listener().completeChallenge(AuthenticationChallengeDisposition::Cancel);
});
return;
}
m_navigationClient->didReceiveAuthenticationChallenge(*this, authenticationChallenge.get());
}
如果是 Legacy TLS 会走 shouldAllowLegacyTLS 函数逻辑:
- 优先判定
-webView:authenticationChallenge:shouldAllowDeprecatedTLS:
; - 其次判定
-_webView:authenticationChallenge:shouldAllowLegacyTLS:
; - 兜底
websiteDataStore().configuration().legacyTLSEnabled()
(默认 true);
而默认情况下,前两步都不会走到,所以这部分理论上不会有漏斗。而且这也是在网络进程判定是 Legacy TLS 且不支持 preconnect 才会走到。
评论记录:
回复评论: