React-native fetch() from https server with self-signed certificate

前端 未结 7 1229
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-11-30 01:39

I\'m trying to communicate with https server having self-signed certificate.

I can do this from .NET application (using ServicePointManager.ServerCertificateValidati

相关标签:
7条回答
  • 2020-11-30 02:15

    This is happened due to self signed certificate used for encryption.Due to security reasons in android it demands C A authority signed or trusted certificates

    use this plugin to avoid this.

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

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

    Add config trusty as true to trust the certificate when you POST or GET API's

    0 讨论(0)
  • 2020-11-30 02:17

    Piggybacking of @Santiago Jimenez Wilson's answer here.

    Obviously patching react-native itself is pretty dirty so we took the suggested override and extracted it into a category.

    Just create a new file called RCTHTTPRequestHandler+yourPatchName.m somewhere in your project:

    //
    //  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
    

    Next step would be to then differ between dev and prod and only overload the method for dev.

    0 讨论(0)
  • 2020-11-30 02:21

    Second answer is for android, and it is working fine. so use that for android. for ios actual answer is correct. but its hard to find the RCTNetwork.xcodeproj and also changes will gone if you delete and add the npm modules. so it shard to maintain.

    so i have created a javascript for patching. just executing the below script with node js will patch. passing the argument -r will remove the patch.

     * 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  ###############');
          }
      }
    });
    
    0 讨论(0)
  • 2020-11-30 02:27

    Disclaimer: This solution should be temporary and documented so that it won't stay in the production phase of the software, this is for development only.

    For iOS, all you have to do is, open your xcodeproject (inside your iOS folder in RN) once you have that open, go to RCTNetwork.xcodeproj and in that project, navigate to RCTHTTPRequestHandler.m

    In that file you will see a line like this:

    #pragma mark - NSURLSession delegate
    

    right after that line, add this function

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

    And voila, you can now make insecure calls to your API without a valid certificate.

    That should be enough, but if you are still having problems, you might need to go to your project's info.plist, left click on it and choose open as... source code.

    and at the end just add

    <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>
    

    so your file will look like this

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

    For a real production ready solution, https://stackoverflow.com/a/36368360/5943130 that solution is better

    0 讨论(0)
  • 2020-11-30 02:29

    Just to add information for users looking for Android Solution. As react-native do not handle SSL Error by default. There is a simple approach to Run your WebView for the websites that must be connected through "https" instead of "http".

    I am assuming you have already installed the react-native-webview module using NPM if no then please google.

    Once you have "react-native-webview" module inside "node_modules" folder. Go inside ".\node_modules >> react-native-webview >> android >> src >> main >> java >> com >> reactnativecommunity >> webview"

    Open "RNCWebViewManager.java" File in Text Editor and Add Below Code

    In import section add these two dependencies

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

    Now Search for Below "class" inside same file protected static class RNCWebViewClient extends WebViewClient

    And add this method

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

    Next Save the file and Build your Project. It would not show Blank page now and handle the Invalid SSL Error.

    Note:

    1. Solution works only if you are using "WebView" from "react-native-webview" instead of deprecated "WebView" from "react-native"
    2. Make sure you have already linked your "react-native" with "react-native-webview" else it would not be included inside your android project
    3. There might be version Error in "RNCWebViewModule", "RNCWebViewManager", "RNCWebViewFileProvider" classes (will be visible once you properly build your project using react-native run-android inside AndroidStudio after opening your android project using import) that you can easily fix using AndroidStudio
    0 讨论(0)
  • 2020-11-30 02:31

    I am also faced the same issue in android. Finally i found the solution for this issue.

    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);
            }
        }
    
    }
    

    and inside our Android application MainApplication.java

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

    Its work for me. May be it will help to all.

    0 讨论(0)
提交回复
热议问题