HttpsURLConnections 默认主机名验证程序

huangapple go评论76阅读模式
英文:

HttpsURLConnections Default Hostname Verifier

问题

我正在使用 HttpURLConnection 来创建一个POST请求(用于获取OAuth2令牌端点处的令牌)。令牌端点使用HTTPS。关于HTTPS的主机名验证是如何工作的,我感到很好奇。HttpsURLConnection 的默认主机名验证器似乎如下所示 [1]:

     /**
     * HostnameVerifier 提供回调机制,以便实现此接口的人可以提供处理情况的策略
     * 当连接的主机与证书中的服务器名不匹配时。
     *
     * 默认实现将拒绝这种连接。
     */
    private static HostnameVerifier defaultHostnameVerifier =
        new HostnameVerifier() {
            public boolean verify(String urlHostname, String certHostname) {
                return false;
            }
        };

我预期我的POST请求会失败,因为这个验证器总是返回 false。但情况并非如此。注释已经说明了有某种回调机制。我不知道的是:defaultHostnameVerifier 是否会验证连接和证书的主机名,还是一个虚拟实现?

我当前的代码如下:

private HttpURLConnection openConnection(String url) throws IOException {
    URL urly = new URL(url);
    final HttpURLConnection con;
    Proxy proxy = getProxy();
    if (proxy == null) {
        con = (HttpURLConnection) urly.openConnection();
    } else {
        con = (HttpURLConnection) urly.openConnection(proxy);
    }

    if (con instanceof HttpsURLConnection) {
        HostnameVerifier verifier = ((HttpsURLConnection) con).getHostnameVerifier(); // 这里已经有一个默认设置
        System.out.println(verifier.getClass().getName());
        
    }
    return con;
}

关于 AsyncHttpClient,我在 [2] 找到了一些解释。由于我目前没有在这个时候使用它,那么使用默认实现是否安全?

[1] https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/com/sun/net/ssl/HttpsURLConnection.java#L76

[2] https://kevinlocke.name/bits/2012/10/03/ssl-certificate-verification-in-dispatch-and-asynchttpclient/

英文:

I'm using a HttpURLConnection in order create a POST request (for fetching a token at some OAuth2 token endpoint). The token endpoint uses HTTPS. I wonder how the hostname verification with regards to HTTPS works. The default hostname verifier of HttpsURLConnection seems to be the following [1]:

     /**
     * HostnameVerifier provides a callback mechanism so that
     * implementers of this interface can supply a policy for
     * handling the case where the host to connect to and
     * the server name from the certificate mismatch.
     *
     * The default implementation will deny such connections.
     */
    private static HostnameVerifier defaultHostnameVerifier =
        new HostnameVerifier() {
            public boolean verify(String urlHostname, String certHostname) {
                return false;
            }
        };

I expected my POST request to fail as this verifier always returns false. This is not the case. The comment already states that there is some kind of callback mechanism. What I do not know is: Does the defaultHostnameVerifier verify the hostname of the connection and the certificate or is it rather a dummy implementation?

My current coding looks like the following piece:

private HttpURLConnection openConnection(String url) throws IOException {
    URL urly = new URL(url);
    final HttpURLConnection con;
    Proxy proxy = getProxy();
    if (proxy == null) {
        con = (HttpURLConnection) urly.openConnection();
    } else {
        con = (HttpURLConnection) urly.openConnection(proxy);
    }

    if (con instanceof HttpsURLConnection) {
        HostnameVerifier verifier = ((HttpsURLConnection) con).getHostnameVerifier(); // there is a default set
        System.out.println(verifier.getClass().getName());
        
    }
    return con;
}

I've found some explanation with regards to the AsyncHttpClient [2]. As I do not use it at this point of time am I safe going with the default implementation?

[1] https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/com/sun/net/ssl/HttpsURLConnection.java#L76

[2] https://kevinlocke.name/bits/2012/10/03/ssl-certificate-verification-in-dispatch-and-asynchttpclient/

答案1

得分: 5

正如顶部的注释所述,该类已不再使用;它曾是抽象的用户可见类,现已由javax.net.HttpsURLConnection替代,您会注意到其代码相同。但是用于 https URL 的实现类是sun.net.www.protocol.https.HttpsURLConnectionImpl,它只是包装(委托给)sun.net.www.protocol.https.DelegateHttpsURLConnection,后者是sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection的子类,而后者在实际进行连接时使用sun.net.www.protocol.HttpsClient,特别是.afterConnect()。作为背景,早期版本的 Java 中的 SSLSocket 并未执行主机名验证,因此 HttpsURLConnection 的实现必须添加它。从 Java 7 开始,SSLSocket 支持 '终端标识',因此当 afterConnect() 认识到此 URL 连接上的 hostnameVerifier 是默认值时,它会关闭 needToCheckSpoofing 并设置 SSLSocket 来执行终端标识。

javax.net.ssl.SSLSocket 同样是一个抽象类,实际上由sun.security.ssl.SSLSocketImpl 和数十个相关类实现,包括sun.security.ssl.ClientHandshaker.serverCertificate(),后者调用可配置的信任管理器,默认情况下是sun.security.ssl.X509TrustManagerImpl,在checkTrusted() 中,由于请求了终端标识,它调用checkIdentity(),后者使用 TYPE_TLS(实际上是指 HTTPS RFC 2818)调用sun.security.util.HostnameChecker来进行实际检查。

你开心了吗?

附:那个网页上关于 HttpsURLConnection.DefaultHostnameVerifier 仅在不匹配时被调用的分析是完全错误的。如上所述,它被绕过,并且实际的实现从未调用它。

另外,我假设您意识到 Java 7 多年来已经不再受支持,除非付费。尽管在我所知道的更近版本中,_此_领域似乎并未更改。Java 11 添加了一个新的java.net.http.HttpClient,其在功能上取代了 [Http,Https]URLConnection。

英文:

As the comments at the top say, that class is no longer used; it was the abstract user-visible class that is now replaced by javax.net.HttpsURLConnection which you will observe has the same code. But the implementation class for https URL is sun.net.www.protocol.https.HttpsURLConnectionImpl which just wraps (delegates to) sun.net.www.protocol.https.DelegateHttpsURLConnection which subclasses sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection which to make the actual connection uses sun.net.www.protocol.HttpsClient and in particular .afterConnect(). As background, in earlier versions of Java SSLSocket did not do hostname verification, so the implementation of HttpsURLConnection had to add it. In Java 7 up, SSLSocket does support 'endpoint identification', so when afterConnect() recognizes that the hostnameVerifier on this URLconnection was the default one, it turns off needToCheckSpoofing and sets the SSLSocket to do endpoint identification.

javax.net.ssl.SSLSocket is similarly an abstract class that is actually implemented by sun.security.ssl.SSLSocketImpl and several dozen related classes including sun.security.ssl.ClientHandshaker.serverCertificate() which calls the configurable trustmanager which by default is sun.security.ssl.X509TrustManagerImpl which in checkTrusted() since endpoint identification was requested calls checkIdentity() which calls sun.security.util.HostnameChecker with TYPE_TLS (actually meaning HTTPS RFC 2818), and that does the actual checking.

Glad you asked?

PS: the analysis on that webpage, that HttpsURLConnection.DefaultHostnameVerifier is called only for mismatch, is quite wrong. As above it is bypassed and never called by the actual implementation.

Also I assume you realize java 7 has not been supported for years unless you pay. Although this area hasn't changed that I know of in more recent versions. Java 11 does add a new java.net.http.HttpClient which functionally supersedes [Http,Https]URLConnection.

huangapple
  • 本文由 发表于 2020年10月16日 20:44:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/64389447.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定