HTTP协议接入解决方案
针对HTTP协议接入情况的解决方案相对简单。一般来说,第三方库都提供相应接口支持修改HTTP请求Header的HOST信息,只需要开发人员将HTTP Header的HOST改为对应的域名即可。
HTTPS协议接入解决方案
Android系统解决方案
证书HOST校验问题
终端在SSL握手过程中会校验当前请求URL的HOST是否在服务端证书的可选域名列表中。例如,假设原本需要请求的URL为https://a.b.com,使用服务器IP直连后实际请求的URL变成https://1.2.3.4。
由于请求的HOST被替换成服务器IP,底层在进行证书的HOST校验时失败,最终导致请求失败。
一般来说,系统都提供相应接口,允许终端设置证书HOST校验实现。因此,利用该接口,将底层默认实现中取终端传入URL的HOST信息(即服务器IP)替换回对应的域名即可解决证书HOST校验问题。
JAVA代码示例:
HostnameVerifier hnv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//示例
if("yourhostname".equals(hostname)){
return true;
} else {
HostnameVerifier hv =
HttpsURLConnection.getDefaultHostnameVerifier();
return hv.verify(hostname, session);
}
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
SNI问题
由于不通过域名而是通过IP直接进行请求。这种情况下,服务端获取到的域名信息为服务器IP,因此请求报文内容中的Host信息为IP,而服务端配置了多个域名,导致无法正确选择域名。
一般来说,系统都提供相应接口,允许终端传入自定义SSLSocketFactory,SSLSocketFactory是用来创建SSLSocket的工厂,SSLSocket是Socket协议的拓展,具有SSL握手功能,且系统提供解决SNI问题的实现类SSLCertificateSocketFactory。因此,利用该方法解决SNI问题。
JAVA代码示例
conn.setSSLSocketFactory(new SSLSocketFactory(){
@Override
public Socket createSocket(Socket s, String host, int port,boolean autoClose) throws IOException{
SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory)SSLCertificateSocketFactory.getDefault(0);
SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(s, realHost,port,autoClose);
sslSocket.setEnableProtocols(sslSocket.getSupportedProtocols());
sslSocketFactory.setHostname(sslSocket, realHost);
return sslSocket;
}
});
iOS系统解决方案
证书HOST校验问题
在NSURLSession的证书校验代理方法URLSession:didReceiveChallenge:completionHandler中增加前置处理,将待验证的 domain由原本的服务器IP转换为其对应的域名,然后再进行后续处理。
Objective-C代码示例
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
// 证书验证前置处理
NSString *domain = challenge.protectionSpace.host; // 获取当前请求的 host(域名或者 IP),假设此时为:1.2.3.4
NSString *testHostIP = self.tempDNS[self.testHost];
// 此时服务端返回的证书里的 CN 字段(即证书颁发的域名)与上述 host 可能不一致,
// 因为上述 host 在发请求前已经被替换为 IP,所以校验证书时会发现域名不一致而无法通过,导致请求被取消
// 所以,需要在校验证书前进行替换处理。
if ([domain isEqualToString:testHostIP]) {
domain = self.testHost; // 替换为对应域名:a.b.com
}
// 以下逻辑与 AFNetworking -> AFURLSessionManager.m 里的代码一致
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:domain]) {
// 上述evaluateServerTrust:forDomain方法用于验证 SSL 握手过程中服务端返回的证书是否可信任,
// 以及请求的 URL 中的域名与证书里声明的的 CN 字段是否一致。
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
其中,关于evaluateServerTrust:forDomain方法的定义,可参考 AFNetworking中AFSecurityPolicy模块的代码,Objective-C代码示例如下所示。
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain {
// 创建证书校验策略
NSMutableArray *policies = [NSMutableArray array];
if (domain) {
// 需要验证请求的域名与证书中声明的 CN 字段是否一致
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 绑定校验策略到服务端返回的证书(serverTrust)
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
// 评估当前 serverTrust 是否可信任,
// 根据苹果官方文档 https://developer.apple.com/library/ios/technotes/tn2232/_index.html
// 当 result 为 kSecTrustResultUnspecified 或 kSecTrustResultProceed 的情况下,serverTrust 可以被验证通过。
SecTrustResultType result;
SecTrustEvaluate(serverTrust, &result);
return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
}
SNI问题
通过使用基于原生支持设置SNI字段的更底层的库(libcurl),解决SNI问题。
Objective-C代码示例
//{HTTPS域名}:443:{IP地址}
NSString *curlHost = ...;
_hosts_list = curl_slist_append(_hosts_list, curlHost.UTF8String);
curl_easy_setopt(_curl, CURLOPT_RESOLVE, _hosts_list);
其中,curlHost形(如{HTTPS域名}:443:{IP地址},_hosts_list)是结构体类型hosts_list,可设置多个IP与Host域名间的映射关系。通过在curl_easy_setopt方法中传入CURLOPT_RESOLVE,将该映射设置到HTTPS请求中,即可达到设置SNI的目的
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。