来自https服务器的具有自签名证书的React-native fetch()

时间:2016-03-29 15:50:48

标签: react-native

我正在尝试与具有自签名证书的https服务器通信。

我可以从.NET应用程序(使用ServicePointManager.ServerCertificateValidationCallback事件),本机iOs应用程序(使用allowsAnyHTTPSCertificateForHost)或Web浏览器(只需要声明证书是可信的)执行此操作。

但我不能让它在react-native应用程序中工作(在Android和iOS模拟器中都没有。)

我尝试了不同的事情,但仍未成功。

我知道那里有一些类似的话题:
Ignore errors for self-signed SSL certs using the fetch API in a ReactNative App?
React Native XMLHttpRequest request fails if ssl (https) certificate is not valid
Fetch in react native wont work with ssl on android
Problems fetching data from a SSL based Server
Unable to make API calls using react-native

但他们要么不包含答案要么不工作(而且他们根本不涉及android编程)。搜索其他资源的效率也不高。

我认为应该有一种简单的方法来使用自签名证书。我错了吗?有人知道吗(iOS和Android都有)?

7 个答案:

答案 0 :(得分:29)

免责声明:此解决方案应该是临时的并且有文档记录,以便它不会停留在软件的生产阶段,这仅用于开发。

对于iOS,您只需打开xcodeproject(在RN中的iOS文件夹中)打开后,转到RCTNetwork.xcodeproj并在该项目中导航到RCTHTTPRequestHandler.m

在该文件中,您将看到如下所示的行:

#pragma mark - NSURLSession delegate

在该行之后,添加此功能

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
  completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}

瞧,您现在可以在没有有效证书的情况下对API进行不安全的调用。

这应该足够了,但是如果你仍然遇到问题,你可能需要转到你的项目的info.plist,左键单击它并选择打开为......源代码。

并在最后添加

<key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
  </dict>

所以你的文件看起来像这样

    ...
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UIViewControllerBasedStatusBarAppearance</key>
    <false/>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string></string>
  <key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
  </dict>
</dict>
</plist>

对于真正的生产就绪解决方案,https://stackoverflow.com/a/36368360/5943130该解决方案更好

答案 1 :(得分:11)

我在android中也面临着同样的问题。最后,我找到了解决此问题的方法。

import com.facebook.react.modules.network.OkHttpClientFactory;
import com.facebook.react.modules.network.OkHttpClientFactory;
import com.facebook.react.modules.network.OkHttpClientProvider;
import com.facebook.react.modules.network.ReactCookieJarContainer;

import java.security.cert.CertificateException;
import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.TimeUnit;


import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;

import static android.content.ContentValues.TAG;

public class CustomClientFactory implements OkHttpClientFactory {
    private static final String TAG = "OkHttpClientFactory";
    @Override
    public OkHttpClient createNewNetworkModuleClient() {
        try {
            // Create a trust manager that does not validate certificate chains
            final TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            // Create an ssl socket factory with our all-trusting manager
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();



            OkHttpClient.Builder builder = new OkHttpClient.Builder()
                    .connectTimeout(0, TimeUnit.MILLISECONDS).readTimeout(0, TimeUnit.MILLISECONDS)
                    .writeTimeout(0, TimeUnit.MILLISECONDS).cookieJar(new ReactCookieJarContainer());
            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
            builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            OkHttpClient okHttpClient = builder.build();
            return okHttpClient;
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            throw new RuntimeException(e);
        }
    }

}

并在我们的Android应用程序MainApplication.java中

 @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);  
    OkHttpClientProvider.setOkHttpClientFactory(new CustomClientFactory()); //add this line.
  }  

对我有用。可能对所有人都有帮助。

答案 2 :(得分:2)

@Santiago Jimenez Wilson回答这里的答案。

显然修补反应原生本身非常脏,所以我们采用建议的覆盖并将其提取到一个类别中。

只需在项目的某处创建一个名为RCTHTTPRequestHandler+yourPatchName.m的新文件:

//
//  RCTHTTPRequestHandler+yourPatchName
//

#import "RCTBridgeModule.h"
#import "RCTHTTPRequestHandler.h"

@implementation RCTHTTPRequestHandler(yourPatchName)

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
  completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
@end

然后下一步将是dev和prod之间的区别,并且只重载dev的方法。

答案 3 :(得分:1)

发生这种情况的原因是用于加密的自签名证书。由于Android中的安全原因,它要求C A权威签名或受信任的证书

使用此插件可以避免这种情况。

https://www.npmjs.com/package/react-native-fetch-blob

RNFetchBlob.config({
  trusty : true
})
.then('GET', 'https://xxxxx.com')
.then((resp) => {
  // ...
})

将配置可信任添加为 true 以在POST或GET API时信任证书

答案 4 :(得分:0)

我通过执行以下操作使它在Android上正常工作:

  1. 在“设置”->“安全性和位置”>“高级”>“加密和凭据”>“从存储安装”下,在设备上安装CA。您可以通过设备上的网络浏览器访问域来确认其安装正确。如果证书有效,则说明已安装CA。
  2. res/xml/network_security_config.xml上创建具有以下内容的网络安全配置。更新域。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <!-- For React Native Hot-reloading system -->
        <!-- If you are running on a device insert your computer IP -->
        <domain includeSubdomains="true">localhost</domain>
        <domain includeSubdomains="true">your self signed domain</domain>

        <trust-anchors>
            <certificates src="system" />
            <certificates src="user" />
        </trust-anchors>
    </domain-config>

    <base-config cleartextTrafficPermitted="false" />
</network-security-config>
  1. 从您的AndroidManifest.xml中引用此配置文件。
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config"
                    ... >
        ...
    </application>
</manifest>

答案 5 :(得分:0)

只需为正在寻找Android解决方案的用户添加信息即可。 作为react-native,默认情况下不处理SSL错误。对于必须通过“ https”而不是“ http”连接的网站,有一种简单的方法可以运行WebView。

我假设您已经使用NPM安装了react-native-webview模块,如果没有,请用google。

一旦您在“ node_modules”文件夹中有“ react-native-webview”模块。 进去 “。\ node_modules >> react-native-webview >> android >> src >> main >> java >> com >> reactnativecommunity >> webview”

在文本编辑器中打开“ RNCWebViewManager.java”文件并添加以下代码

在导入部分添加这两个依赖项

....
import android.webkit.SslErrorHandler;
import android.net.http.SslError;
....

现在在同一文件内搜索以下“类”     受保护的静态类RNCWebViewClient扩展了WebViewClient

并添加此方法

@Override
public void onReceivedSslError(WebView webView, SslErrorHandler handler, SslError 
error) {
    if (error.toString() == "piglet")
        handler.cancel();
    else
        handler.proceed(); // Ignore SSL certificate errors
}

下一步保存文件并构建您的项目。它不会立即显示空白页并处理无效的SSL错误。

注意:

  1. 仅当您使用“ react-native-webview”中的“ WebView”而不是“ react-native”中已弃用的“ WebView”时,解决方案才有效
  2. 确保您已将“ react-native”与“ react-native-webview”链接,否则不会包含在您的android项目中
  3. “ RNCWebViewModule”,“ RNCWebViewManager”,“ RNCWebViewFileProvider”类中可能存在版本错误(在通过导入打开android项目后,在AndroidStudio中使用react-native run-android正确构建项目后,该错误才可见)使用AndroidStudio轻松修复

答案 6 :(得分:0)

第二个答案是针对android的,它工作正常。因此将其用于android。 对于ios的实际答案是正确的。但是很难找到RCTNetwork.xcodeproj,并且如果删除并添加npm模块,更改也将消失。因此很难维护。

所以我创建了一个用于修补的JavaScript。 只需使用node js执行以下脚本即可进行修补。 传递参数-r将删除补丁。

 * run this script normally for patching,
 * pass -r argument at the end of the command for removing the patch
 * ex: 
 * patching:         $ node patch_ssl_bypass.js
 * removing patch:   $ node patch_ssl_bypass.js -r
 */

var fs = require('fs');

const isRemove = process.argv[process.argv.length-1] == '-r';

const file =
  'node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm';
const delemeter = '#pragma mark - NSURLSession delegate';
const code = `
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
  completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
`;
console.log('#############   Reading File   ###############');
fs.readFile(file, 'utf8', function(error, data) {
  if (error) {
    console.log('#############  error reading file  ###############');
    console.error(error);
    return;
  }
  if (data.indexOf(code) < 0 && !isRemove) {
    console.log('#############  Patch is not done.  ###############');
    console.log('#############  Patching file  ###############');
    var parts = data.split(delemeter);
    var newCodeBlock = parts[0] + delemeter + '\n' + code + '\n'+parts[1];
    fs.writeFile(file,newCodeBlock,function(){
        console.log('#############  Successfully patched file  ###############');
        console.log('#############  re build the ios project  ###############');
    })
  }else{
      if (isRemove){
          var updatedCode = data.replace(code,'');
        fs.writeFile(file,updatedCode,function(){
            console.log('#############  Successfully removed patch  ###############');
            console.log('#############  re build the ios project  ###############');
        })
      }else{
        console.log('#############  File already patched. No need again  ###############');
      }
  }
});