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

Posted

技术标签:

【中文标题】来自带有自签名证书的 https 服务器的 React-native fetch()【英文标题】:React-native fetch() from https server with self-signed certificate 【发布时间】:2016-07-17 07:02:25 【问题描述】:

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

我可以从 .NET 应用程序(使用 ServicePointManager.ServerCertificateValidationCallback 事件)、本机 ios 应用程序(使用allowAnyHTTPSCertificateForHost)或网络浏览器(只需声明证书是可信的)执行此操作。

但我无法让它在 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 validFetch in react native wont work with ssl on androidProblems fetching data from a SSL based ServerUnable to make API calls using react-native

但它们要么不包含答案,要么不工作(而且它们根本不包括 android 编程)。搜索其他资源也没有成效。

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

【问题讨论】:

Ignore errors for self-signed SSL certs using the fetch API in a ReactNative App?的可能重复 感谢您的指点。解决方案不是字面上的自我感叹证书,但我们会尝试。 对 - 我不认为自签名证书是有用的方法,所以在这方面,你的问题有点 X/Y 问题。 xyproblem.info 我不确定是否有任何方法可以让自签名证书与 React Native 一起使用。我认为你需要编写一些 Objective-C 来允许它,这只是在问题上贴上一个讨厌的创可贴,而不是真正解决它。 如果您使用 Expo,请在此处查看我的回答:***.com/a/70775576/4350421。希望它有所帮助:) 【参考方案1】:

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

对于 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>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
        <key>subdomain.example.com</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </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>
  <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
        <key>subdomain.example.com</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>
</plist>

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

【讨论】:

我会在一周左右的时间内添加 Android 的答案,如果您现在需要,请告诉我,也许我可以向您展示到目前为止我在 Android 中所做的工作 它在 iOS 中做到了!期待安卓的解决方案。 你能帮我看看如何为 android 做这个吗?最好的问候 @SantiagoJimenezWilson:你有 2017 年的 Android 答案吗?谢谢。 @svlada 是的,您绝对应该购买 SSL 证书。这种hackityhack永远不应该在生产中使用【参考方案2】:

我在 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.
    

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

【讨论】:

你能解释一下吗?您正在创建一个具有什么名称的新文件? MainApplication.java 中的任何导入? 我遇到了这个错误找不到符号 OkHttpClientProvider.setOkHttpClientFactory(new CustomClientFactory()); 我通过在 CustomClientFactory 文件 package com.your_package_name_here;987654323@ 的最顶部添加包名称使其工作。 这对我有用(Y) 也在MainApplication中添加“import com.facebook.react.modules.network.OkHttpClientProvider;”【参考方案3】:

我通过执行以下操作在 Android 上运行:

    在您的设备上安装 CA,在设置 -> 安全和位置 > 高级 > 加密和凭据 > 从存储安装。您可以通过设备上的网络浏览器访问域来确认它已正确安装。如果证书通过验证,则说明 CA 已安装。 在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>
    从您的AndroidManifest.xml 引用此配置文件。
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config"
                    ... >
        ...
    </application>
</manifest>

【讨论】:

它在我实施了几天后随机开始工作!现在,如果我从AndroidManifest.xml 中删除它,它会立即打开/关闭。去图:P【参考方案4】:

这是由于用于加密的自签名证书。由于 android 中的安全原因,它需要 C A 权威签名或受信任的证书

使用这个插件来避免这种情况。

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

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

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

【讨论】:

截至 2021 年 3 月,RNFetchBlob 提供了各种问题来开始工作;调试4小时无结果 您应该知道,RNFetchBlob 的响应对象与 RN 提供的 fetch 方法的响应对象略有不同。因此,您很有可能也应该更改响应解析逻辑。 感谢这个答案,我得到了它的工作!这是怎么做的: RNFetchBlob.config( trusty: true ) .fetch('GET', '195.756.2.3:3000') .then((response: any) => console.log('response.data:', response .data)) .catch((error: any) => console.log('fetch failed with this error: ', error); );【参考方案5】:

这里是@Santiago Jimenez Wilson 的答案。

显然,修补 react-native 本身很脏,所以我们采用了建议的覆盖并将其提取到一个类别中。

只需在项目的某处创建一个名为 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 重载方法。

【讨论】:

你用的是什么版本的 RN? 那是 react-native 0.38 您是否尝试在 0.40 版中解决此问题?用户代码看不到 RCTHTTPRequestHandler.h 存在问题。 @leizeQ 使用 #import #import @EricKim 无法按照此处的说明工作:***.com/questions/41997905/…【参考方案6】:

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

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

一旦你在“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

下一步保存文件并构建您的项目。它现在不会显示空白页面并处理 Invalid SSL Error。

注意:

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

【讨论】:

【参考方案7】:

第二个答案是针对 android 的,它运行良好。所以将它用于android。 对于 ios 实际答案是正确的。但是很难找到 RCTNetwork.xcodeproj,如果您删除并添加 npm 模块,更改也会消失。所以它很难维护。

所以我创建了一个用于修补的 javascript。 只需使用节点 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  ###############');
      
  
);

【讨论】:

以上是关于来自带有自签名证书的 https 服务器的 React-native fetch()的主要内容,如果未能解决你的问题,请参考以下文章

Apollo 客户端自签名证书

AWS ELB -> 带有自签名证书的 HTTPS 后端服务器

带有自签名证书的 ktor 客户端 https 请求

带有自签名 SSL 证书的 iOS MKTileOverlay

Jenkins 无法使用带有自签名证书的 HTTPS (HTTP + SSL) 连接到 SonarQube 服务器

为啥我使用来自 Angular 服务的自签名证书调用 HTTPS API 时会出现此 ERR_CERT_AUTHORITY_INVALID 错误?