使用Android Volley发出HTTPS请求

60

我正在尝试使用以下代码进行 https 请求:

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
request = new Request<String>(Request.Method.GET,"https://devblahblahblah.com/service/etc",errListener);

但是我出现了这个错误:

  

com.android.volley.NoConnectionError:   javax.net.ssl.SSLHandshakeException:   java.security.cert.CertPathValidatorException:找不到认证路径的信任锚点。

需要注意两点:

  1. HTTPS证书是有效的,在浏览器中打开时没有任何警告。
  2. 以上代码在HTTP链接上正常工作。

实际上,我需要知道Android Volley框架中是否有任何开关/选项,通过使用它,我将成功地访问HTTPS URL?


1
我的猜测是,您需要像直接使用HttpUrlConnection一样配置它。请参阅http://commonsware.com/blog/2013/03/04/ssl-android-basics.html和http://nelenkov.blogspot.ie/2011/12/using-custom-certificate-trust-store-on.html。 - CommonsWare
4
我找到了一个名为 HurlStack 的类,它扩展了 HttpStack,是传递给 Volley.newRequestQueue 方法的可选参数。HurlStack 的构造函数接受一个类型为 SSLSocketFactory 的参数,并在其 javadoc 中写道:“用于 HTTPS 连接的 SSL 工厂”,但我还没有尝试过它。 - Abdullah Shoaib
10个回答

60

警告:以下代码不应在生产环境中使用,因为它容易受到SSL攻击

也许下面的代码对你有所帮助:

1.创建一个实现X509TrustManager接口的HttpsTrustManager类:

public class HttpsTrustManager implements X509TrustManager {

    private static TrustManager[] trustManagers;
    private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};

    @Override
    public void checkClientTrusted(
            java.security.cert.X509Certificate[] x509Certificates, String s)
            throws java.security.cert.CertificateException {

    }

    @Override
    public void checkServerTrusted(
            java.security.cert.X509Certificate[] x509Certificates, String s)
            throws java.security.cert.CertificateException {

    }

    public boolean isClientTrusted(X509Certificate[] chain) {
        return true;
    }

    public boolean isServerTrusted(X509Certificate[] chain) {
        return true;
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return _AcceptedIssuers;
    }

    public static void allowAllSSL() {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }

        });

        SSLContext context = null;
        if (trustManagers == null) {
            trustManagers = new TrustManager[]{new HttpsTrustManager()};
        }

        try {
            context = SSLContext.getInstance("TLS");
            context.init(null, trustManagers, new SecureRandom());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }

        HttpsURLConnection.setDefaultSSLSocketFactory(context
                .getSocketFactory());
    }

}

2. 在进行 HTTPS 请求之前添加 HttpsTrustManager.allowAllSSL()

HttpsTrustManager.allowAllSSL();
String  tag_string_req = "string_req";
StringRequest strReq = new StringRequest(Request.Method.POST,
        your_https_url, new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        Log.d(TAG, "response :"+response);
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        VolleyLog.d(TAG, "Error: " + error.getMessage());
    }
}){
    @Override
    protected Map<String, String> getParams() {
        Map<String, String> params = new HashMap<String, String>();
        params.put("username", "max");
        params.put("password", "123456");
        return params;
    }
};
AppController.getInstance().addToRequestQueue(strReq, tag_string_req);

22
除了你实际上没有验证服务器证书以外,这一切都很好。请确保不要在生产环境中部署此代码,除非你想让你的用户面临各种 SSL 攻击风险。 - asgs
5
仅供参考,切勿投入生产。 - Lawrence Gimenez
这似乎是在本地主机上自签名证书的简单解决方案。 - Charlie Wu
这个在 Volley 中是如何工作的,就像海报所问的那样? - portfoliobuilder
2
如果你使用这个,你的应用将会在Google Play被拒绝。 - apinho
显示剩余2条评论

16

你可以添加这个类并从onCreate方法中执行它

new NukeSSLCerts().nuke();

这会使得Volley信任所有SSL证书。


1
另一种方法是使用Certbot,提供免费的签名证书,但仅限于90天,我们必须在每90天后进行更新。 - Samrat Das
2
非常适合开发。 - Naveed Ahmad
4
自2017年3月1日起,谷歌可以阻止使用不安全的HostnameVerifier实现的任何新应用程序或更新发布。您已发布的APK版本将不受影响,但是除非您解决此漏洞,否则将阻止对该应用程序的任何更新。详细信息请参见:https://support.google.com/faqs/answer/7188426。 - MaxF
在我给出的第二条评论中,@MaxF,我提供了另一种方法。 对于所有人来说,只有在开发模式下才使用nukeSSl类。 - Samrat Das

4

如果你正在使用Volley并且想要进行HTTPS请求或SSL认证服务,则可以选择以下最简单的方法:

步骤1. 将 .cer 文件放入 res/raw/ 文件夹。

步骤2. 使用此方法,并将 .cer 文件名替换为您的 .cer 文件,并替换您的主机名。

private SSLSocketFactory getSocketFactory() {

    CertificateFactory cf = null;
    try {

        cf = CertificateFactory.getInstance("X.509");
        InputStream caInput = getResources().openRawResource(R.raw.cert_name);
        Certificate ca;
        try {

            ca = cf.generateCertificate(caInput);
            Log.e("CERT", "ca=" + ((X509Certificate) ca).getSubjectDN());
        } finally {
            caInput.close();
        }


        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);


        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);


        HostnameVerifier hostnameVerifier = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {

                Log.e("CipherUsed", session.getCipherSuite());
                return hostname.compareTo("10.199.89.68")==0; //The Hostname of your server.

            }
        };


        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
        SSLContext context = null;
        context = SSLContext.getInstance("TLS");

        context.init(null, tmf.getTrustManagers(), null);
        HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());

        SSLSocketFactory sf = context.getSocketFactory();


        return sf;

    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    }

    return  null;
}

步骤 --> 3. 在Volley请求中,将该行代码“RequestQueue queue = Volley.newRequestQueue(this);”替换为“RequestQueue queue = Volley.newRequestQueue(this, new HurlStack(null, getSocketFactory()));”,以使用HurlStack来请求网络。


4
到目前为止,唯一的答案是关于添加不受信任证书作为解决方案,但由于您的浏览器没有抱怨,这通常意味着Volley无法找到完整的可信链,其中包含中间证书。
我用LetsEncrypt证书遇到过这种情况。大多数浏览器已经拥有中间证书,因此在浏览器上看起来一切正常,但是Volley显然缺少了某些内容。
解决方案
将中间证书添加到您的Web服务器配置中。对于Apache,您可以参考此文献:
https://access.redhat.com/solutions/43575
对于LetsEncrypt,它具体是这个文件:/etc/letsencrypt/live/your.website.com/chain.pem因此,除了您已经使用的CertificateFile和KeyFile之外,您现在还有第三行:
SSLCertificateChainFile /etc/letsencrypt/live/your.website.com/chain.pem

只需添加该行,重新启动apache,Volley就不会再抱怨,而且您也没有引入任何安全漏洞!


谢谢,这很有帮助。虽然我一开始不太清楚需要编辑哪个文件,但最终我还是弄明白了。对我来说有些不同,因为我需要在Node.js中进行编辑。但还是非常感谢您。 - Tsepo Nkalai
此外,要真正跟踪可能的 SSL 设置错误,您可以使用此工具测试您的网站:https://www.ssllabs.com/ssltest/index.html 如果在使用该工具后,您突然变得着迷于获得 A+ 分数,Mozilla 有一个有用的 SSL 配置生成器:https://mozilla.github.io/server-side-tls/ssl-config-generator/ 现代版本通常可以让您轻松获得 A+ 分数,没有任何问题。 - Paul

1

当我为域名添加SSL时,我遇到了同样的问题。经过两天的努力,我发现了解决方案:URL出现了错误。我之前使用的是https://example.com,但是当我为域名添加SSL后,URL会发生变化。

https://www.example.com

而且POST方法也正常工作


1

我无法打开@Ogre_BGR提供的链接,但在浏览网页时,我发现以下实际实现已经完成smanikandan14 Github。请查看他的SSL连接说明以了解更多信息。


0
对于任何遇到这样问题的人,如果你使用 Letsencrypt 来进行 SSL 证书管理,同时使用 node.js 作为 Web 服务器,请尝试以下方法。假设你的代码类似于这样。我通过添加以下代码解决了这个问题:const chain = fs... 希望这能帮到你。
...
const app = express();
const privateKey  = fs.readFileSync('ssl/privkey.pem', 'utf8');
const certificate = fs.readFileSync('ssl/cert.pem', 'utf8');
const chain = fs.readFileSync('ssl/chain.pem', 'utf8');
const credentials = {key: privateKey, cert: certificate, ca: chain};
...
var httpsServer = https.createServer(credentials, app);

0

这种情况可能有几个原因:

  • 颁发服务器证书的CA是未知的
  • 服务器证书没有由CA签名,而是自签名的
  • 服务器配置缺少中间CA

来自安卓官方文档的链接

解决方法: 您可以在请求中提供一个证书文件


0

当我关闭Cloudflare代理时,遇到了这个错误 在这里检查图片 解决此问题的最佳方法是您可以重新打开代理,并在SSL证书上添加完全安全访问。


0
如果有人使用 nginxletsencrypt 的 SSL 证书,解决方案是直接使用文件 fullchain.pem 中的证书,而不是 cert.pem 文件中的证书:
ssl_certificate     /.../fullchain.pem;

该文件包含您的证书和CA的连接。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接