这是大概9月中旬写的文章,但是现在才发表,是因为有其他的事情再忙。
Android中React Native网络模块okhttp支持SSL
前言:我们和第三方公司检查我们的APP,发现一些问题,其中就有React Native中网络模块okhttp不支持SSL验证,因此要修改React Native原生代码。
阅读React Navive源代码
其实RN已经提供了一个默认修改okhttpclient的方法,NetworkingModule.java文件就是我们Android最底层的网络请求工具类,每次创建NetworkingModuled对象的时候,我们会传入一个okhttpclient的对象:
public NetworkingModule(ReactApplicationContext context) {
this(context, (String)null, OkHttpClientProvider.createClient(), (List)null);
}
public NetworkingModule(ReactApplicationContext context, List<NetworkInterceptorCreator> networkInterceptorCreators) {
this(context, (String)null, OkHttpClientProvider.createClient(), networkInterceptorCreators);
}
public NetworkingModule(ReactApplicationContext context, String defaultUserAgent) {
this(context, defaultUserAgent, OkHttpClientProvider.createClient(), (List)null);
}
可以看到,每次都是通过:
OkHttpClientProvider.createClient()
的方式创建了一个okhttpclient对象。
所以我们只需要替换掉默认额httpclient ,然后为其添加上证书认证就可以了,我们打开OkHttpClientProvider这个工具类查看源码。发现这个是一个单列,并且提供了一个修改默认okhttpclient的方法,我们可以查看这个:
// Centralized OkHttpClient for all networking requests.
private static @Nullable OkHttpClient sClient;
public static OkHttpClient getOkHttpClient() {
if (sClient == null) {
sClient = createClient();
}
return sClient;
}
// okhttp3 OkHttpClient is immutable
// This allows app to init an OkHttpClient with custom settings.
public static void replaceOkHttpClient(OkHttpClient client) {
sClient = client;
}
public static OkHttpClient createClient() {
// No timeouts by default
OkHttpClient.Builder client = new OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.MILLISECONDS)
.readTimeout(0, TimeUnit.MILLISECONDS)
.writeTimeout(0, TimeUnit.MILLISECONDS)
.cookieJar(new ReactCookieJarContainer());
return enableTls12OnPreLollipop(client).build();
}
我们通过阅读上面源码发现,RN写了一个单列的okhttpclient但是没有开放方法支持,创建httpclient的时候都是用的createClient()这个方法,没有使用sClient。所以在NetworkingModuled里面我们需要把传入的okhttpclient改成传入sClient。如下:
/**
* @param context the ReactContext of the application
*/
public NetworkingModule(final ReactApplicationContext context) {
this(context, null, OkHttpClientProvider.getOkHttpClient(), null);
}
/**
* @param context the ReactContext of the application
* @param networkInterceptorCreators list of {@link NetworkInterceptorCreator}'s whose create()
* methods would be called to attach the interceptors to the client.
*/
public NetworkingModule(
ReactApplicationContext context,
List<NetworkInterceptorCreator> networkInterceptorCreators) {
this(context, null, OkHttpClientProvider.getOkHttpClient(), networkInterceptorCreators);
}
/**
* @param context the ReactContext of the application
* @param defaultUserAgent the User-Agent header that will be set for all requests where the
* caller does not provide one explicitly
*/
public NetworkingModule(ReactApplicationContext context, String defaultUserAgent) {
this(context, defaultUserAgent, OkHttpClientProvider.getOkHttpClient(), null);
}
将NetworkingModuled中OkHttpClientProvider.createClient()替换成OkHttpClientProvider.getOkHttpClient(),即可。
但是有人说这里是RN包里面的源码如何修改。这里需要你把源码考出来,然后自己修改。然后在你的项目里面替换成你的修改后的代码。
如何修改RN源码
核心操作:我们直接copy一份NetworkingModule,Mainreactpackage和RequestBodyUtil(NetworkingModule需要)的代码。然后在application中把mainreactpackage替换成我们copy出去的那一份,
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
//替换成我们copy的那个文件
new MainReactPackage(),
new VectorIconsPackage(),
new RCTSplashScreenPackage(),
new RNDeviceInfo(),
new AndroidModulePackage(),
new RCTSwipeRefreshLayoutPackage()
);
}
};
然后点进我们copy的那个MainReactPackage文件,再把MainReactPackage中的:
new ModuleSpec(NetworkingModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
//替换成我们自己copy的那个文件
return new NetworkingModule(context);
}
}),
这里支持修改NetworkingModule,也可以支持修改其他源码的方式。
okhttp支持SSL
在你的项目中Application修改RN默认的okhttpclient,并且支持SSL
首先在你的Application文件,在oncreate方法中提供其方法:
@Override
public void onCreate() {
。。。
//RN OKHTTP添加https证书
OkHttpClientProvider.replaceOkHttpClient(initCustomOkHttpClient());
。。。
}
public OkHttpClient initCustomOkHttpClient() {
if (BuildConfig.DEBUG) {
//RN原生的请求体 在debug上不替换
return OkHttpClientProvider.createClient();
}
OkHttpClient.Builder client = new OkHttpClient.Builder()
.connectTimeout(30 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(30 * 1000, TimeUnit.MILLISECONDS)
.writeTimeout(30 * 1000, TimeUnit.MILLISECONDS)
.cookieJar(new ReactCookieJarContainer());
client.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
try {
// //你的证书文件,放在android的assets文件夹下
setCertificates(client, getAssets().open("new_server_9fcn.pem"));
client.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
Logger.e("认证成功");
return true;
}
});
} catch (IOException e) {
Logger.e("e", e.toString());
e.printStackTrace();
}
OkHttpClient.Builder builder = OkHttpClientProvider.enableTls12OnPreLollipop(client);
return builder.build();
}
public void setCertificates(OkHttpClient.Builder client, InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certificate : certificates) {
String certificateAlias = Integer.toString(index++);
Certificate ca=certificateFactory.generateCertificate(certificate);
keyStore.setCertificateEntry(certificateAlias, ca);
try {
if (certificate != null)
certificate.close();
} catch (IOException e) {
e.printStackTrace();
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(
null,
trustManagerFactory.getTrustManagers(),
new SecureRandom()
);
client.sslSocketFactory(sslContext.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
Logger.e("e", e.toString());
}
}
修改NetworkingModule.java文件
/**
* @param context the ReactContext of the application
*/
public NetworkingModule(final ReactApplicationContext context) {
this(context, null, OkHttpClientProvider.getOkHttpClient(), null);
}
/**
* @param context the ReactContext of the application
* @param networkInterceptorCreators list of {@link NetworkInterceptorCreator}'s whose create()
* methods would be called to attach the interceptors to the client.
*/
public NetworkingModule(
ReactApplicationContext context,
List<NetworkInterceptorCreator> networkInterceptorCreators) {
this(context, null, OkHttpClientProvider.getOkHttpClient(), networkInterceptorCreators);
}
/**
* @param context the ReactContext of the application
* @param defaultUserAgent the User-Agent header that will be set for all requests where the
* caller does not provide one explicitly
*/
public NetworkingModule(ReactApplicationContext context, String defaultUserAgent) {
this(context, defaultUserAgent, OkHttpClientProvider.getOkHttpClient(), null);
}
将NetworkingModuled中OkHttpClientProvider.createClient()替换成OkHttpClientProvider.getOkHttpClient(),即可。
到这里就完了。
总结
通过修改RN源码方式我们可以让okhttp支持SSL,同样的,如果那个部分RN代码不符合我们现在的需求,也可以通过这种方式修改。