JitsiMeet是专注音视频会议的的一个开源项目,并且通过ReactNative进行跨平台,所以JitsiMeet本质上也是一个ReactNative项目;JitsiMeet项目开发中App会连接后端的各种环境地址,你会遇到一些环境是Https地址,但这些Https上用的证书可能是自建的或者使用别的域名的证书,总之就是证书无法匹配到根证书,无法通过验证。于是为了能和后端愉快的通信,我就得处理在这些环境下忽略Https证书的验证。
ReactNative在JS层并没有提供Https证书操作的接口,并且ReactNative网络最终是靠原生层的代码去实现的, 所以问题的解决思路是通过修改IOS和Android原生代码来实现忽略Https证书的验证。
要忽略证书验证的场景有以下两种:
- WebView
- Api Request
Webview
首先我们来看一下 WebView的解决方案。
var RCTWebView = requireNativeComponent('RCTWebView', WebView, WebView.extraNativeComponentConfig);
以上代码出现在 webview.android.js 和 webview.ios.js 中,通过搜索可以发现对应的Native代码。
Android:
react-native/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java.
IOS:
React/Views/RCTWebViewManager.m
在Android的Webview使用了自定义的WebViewClient, 而WebViewClient中有函数onReceivedSslError
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.cancel();
}
当WebView加载Https链接发生证书错误时会回调这个函数。函数默认返回值是 handler.cancel(), 去取消当前地址的加载,所以会显示一空白页。那么想忽略错误继续加载地址,可以改成如下:
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
需要修改上述的函数就得自己重写ReactWebViewManager.java,为了保证暴露给JS层的ClassName也是RTCWebView,必须实现canOverrideExistingModule,来覆盖系统的Module。
@Override
public boolean canOverrideExistingModule() {
return true;
}
接下来看IOS的RCTWebViewManager.m, 发现并不像Android可以覆盖系统的Module。不过幸好IOS有全局扩展类的语法。通过在AppDelegate.m中 加上对NSURLRequest进行扩展加上忽略证书验证。
@implementation NSURLRequest (AllowAnyHTTPSCertificate)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host
{
return YES;
}
@end
Api Request
在Android中是JS层网络请求最终使用的是 Java层的 NetworkingModule类, 类中使用OkHttp处理网络请求,所以我们只需要给Okhttp加上忽略证书验让的设置。主要代码如下:
private SSLContext getSLLContext () {
X509TrustManager xtm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] x509Certificates = new X509Certificate[0];
return x509Certificates;
}
};
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{xtm}, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return sslContext;
}
NetworkingModule(
ReactApplicationContext reactContext,
@Nullable String defaultUserAgent,
OkHttpClient client,
@Nullable List<NetworkInterceptorCreator> networkInterceptorCreators) {
super(reactContext);
if (networkInterceptorCreators != null) {
OkHttpClient.Builder clientBuilder = client.newBuilder();
for (NetworkInterceptorCreator networkInterceptorCreator : networkInterceptorCreators) {
clientBuilder.addNetworkInterceptor(networkInterceptorCreator.create());
}
client = clientBuilder.build();
}
client = client.newBuilder().hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}).sslSocketFactory(getSLLContext().getSocketFactory()).build();
mClient = client;
mCookieHandler = new ForwardingCookieHandler(reactContext);
mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
mShuttingDown = false;
mDefaultUserAgent = defaultUserAgent;
mRequestIds = new HashSet<>();
}
@Override
public boolean canOverrideExistingModule() {
return true ;
}
在IOS中,同样我们只需要在AppDelegate.m中去扩展RCTHTTPRequestHandler 即可。
@implementation RCTHTTPRequestHandler (RCTHTTPRequestHandlerSSL)
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
@end
测试验证
经测试,JitsiMeet项目经过上述方法修改运行后,在IOS端上已经成功忽略证书验证,但在安卓端却没有效果,仍然需要验证证书;
这是因为JitsiMeet项目安卓端引用的是./node_modules/react-native/android/com/facebook/react/react-native的react-native-x.xx.x.aar(x.xx.x是react-native版本)已经预先编译好的包,而不是./node_modules/react-native下的源码。所以这里需要通过以下步骤才能使得在安卓端上成功忽略https证书:
1. 解压出aar包中的classes.jar包
2. 使用jd-gui 反编译出ReactWebViewManager.java和NetworkingModule.java
3. 对ReactWebViewManager.java和NetworkingModule.java分别添加上述的修改内容
4. 使用 javac 将ReactWebViewManager.java和NetworkingModule.java编译成.class字节码文件
5. 通过 jar -uvf classes.jar class文件将步骤4生成的字节码文件更新到原本的classes.jar中
6. 将更新后的classes.jar重新压缩成aar包并替换./node_modules/react-native/android/com/facebook/react/react-native的react-native-x.xx.x.aar
此时再次运行安卓端,可以看到https证书忽略效果生效。