【问题标题】:Using HTTPS with REST in Java在 Java 中使用 HTTPS 和 REST
【发布时间】:2010-12-17 22:56:52
【问题描述】:

我有一个用 Grizzly 制作的 REST 服务器,它使用 HTTPS 并与 Firefox 完美配合。代码如下:

//Build a new Servlet Adapter.
ServletAdapter adapter=new ServletAdapter();
adapter.addInitParameter("com.sun.jersey.config.property.packages", "My.services");
adapter.addInitParameter(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, SecurityFilter.class.getName());
adapter.setContextPath("/");
adapter.setServletInstance(new ServletContainer());

//Configure SSL (See instructions at the top of this file on how these files are generated.)
SSLConfig ssl=new SSLConfig();
String keystoreFile=Main.class.getResource("resources/keystore_server.jks").toURI().getPath();
System.out.printf("Using keystore at: %s.",keystoreFile);
ssl.setKeyStoreFile(keystoreFile);
ssl.setKeyStorePass("asdfgh");

//Build the web server.
GrizzlyWebServer webServer=new GrizzlyWebServer(getPort(9999),".",true);

//Add the servlet.
webServer.addGrizzlyAdapter(adapter, new String[]{"/"});

//Set SSL
webServer.setSSLConfig(ssl);

//Start it up.
System.out.println(String.format("Jersey app started with WADL available at "
  + "%sapplication.wadl\n",
        "https://localhost:9999/"));
webServer.start();

现在,我尝试用 Java 实现它:

SSLContext ctx=null;
try {
    ctx = SSLContext.getInstance("SSL");
} catch (NoSuchAlgorithmException e1) {
    e1.printStackTrace();
}
ClientConfig config=new DefaultClientConfig();
config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, new HTTPSProperties(null,ctx));
WebResource service=Client.create(new DefaultClientConfig()).resource("https://localhost:9999/");

//Attempt to view the user's page.
try{
    service
        .path("user/"+username)
        .get(String.class);
}

得到:

com.sun.jersey.api.client.ClientHandlerException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:128)
 at com.sun.jersey.api.client.Client.handle(Client.java:453)
 at com.sun.jersey.api.client.WebResource.handle(WebResource.java:557)
 at com.sun.jersey.api.client.WebResource.get(WebResource.java:179)

从我在网上找到的示例来看,我似乎需要设置一个 Truststore,然后设置某种 TrustManager。对于我的简单小项目来说,这似乎有很多代码和设置工作。有没有更简单的方法可以说..我信任这个证书并指向一个 .cert 文件?

【问题讨论】:

    标签: java rest ssl x509


    【解决方案1】:

    当您说“有没有更简单的方法来...信任此证书”时,这正是您通过将证书添加到 Java 信任库中所做的事情。这非常非常容易做到,您无需在客户端应用程序中执行任何操作即可让信任库得到认可或利用。

    在您的客户端计算机上,找到您的 cacerts 文件所在的位置(这是您的默认 Java 信任库,默认情况下位于 /lib/security/certs/cacerts。

    然后,输入以下内容:

    keytool -import -alias <Name for the cert> -file <the .cer file> -keystore <path to cacerts>
    

    这会将证书导入您的信任存储区,之后,您的客户端应用将能够毫无问题地连接到您的 Grizzly HTTPS 服务器。

    如果您不想将证书导入您的默认信任存储区——即,您只希望它可用于这个客户端应用程序,而不是您在该机器上的 JVM 上运行的任何其他应用程序——然后您可以为您的应用创建一个新的信任库。不要将 keytool 传递到现有的默认 cacerts 文件的路径,而是将 keytool 传递到新的信任存储文件的路径:

    keytool -import -alias <Name for the cert> -file <the .cer file> -keystore <path to new trust store>
    

    系统将要求您设置并验证信任存储文件的新密码。然后,当您启动客户端应用程序时,使用以下参数启动它:

    java -Djavax.net.ssl.trustStore=<path to new trust store> -Djavax.net.ssl.trustStorePassword=<trust store password>
    

    简单俗气,真的。

    【讨论】:

    • 非常好,但是...我可以在代码中设置 trustStore 和 trustStorePassword 而不是 VM 的参数吗?
    • 是的——在调用任何需要 SSL 的方法之前,使用 System.setProperty("javax.net.ssl.trustStore", "),然后做同样的事情对于 javax.net.ssl.trustStorePassword。
    • 一个可悲的部分是不正确的文件名给出了一个非常模糊的异常:“java.security.InvalidAlgorithmParameterException: trustAnchors 参数必须是非空的”
    • 1.在 java8 中,默认信任库的路径是 /lib/security/cacerts(注意缺少 /cert/); 2. 当我执行命令时,它会询问我一个密码 O_o 我必须输入什么?
    • Java 信任库的默认密码是“changeit”。
    【解决方案2】:

    这是痛苦的路线:

        SSLContext ctx = null;
        try {
            KeyStore trustStore;
            trustStore = KeyStore.getInstance("JKS");
            trustStore.load(new FileInputStream("C:\\truststore_client"),
                    "asdfgh".toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory
                    .getInstance("SunX509");
            tmf.init(trustStore);
            ctx = SSLContext.getInstance("SSL");
            ctx.init(null, tmf.getTrustManagers(), null);
        } catch (NoSuchAlgorithmException e1) {
            e1.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        ClientConfig config = new DefaultClientConfig();
        config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES,
                new HTTPSProperties(null, ctx));
    
        WebResource service = Client.create(config).resource(
                "https://localhost:9999/");
        service.addFilter(new HTTPBasicAuthFilter(username, password));
    
        // Attempt to view the user's page.
        try {
            service.path("user/" + username).get(String.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
    

    一定要喜欢这六个不同的捕获异常:)。当然有一些重构来简化代码。但是,我喜欢虚拟机上 delfuego 的 -D 选项。我希望有一个我可以设置的 javax.net.ssl.trustStore 静态属性。只需要两行代码就搞定了。有人知道那会是哪里吗?

    这可能要求太多,但理想情况下不会使用 keytool。相反,trustedStore 将由代码动态创建,并在运行时添加证书。

    一定有更好的答案。

    【讨论】:

    • 投票赞成,因为痛苦的路线是我正在寻找的(稍微复杂一点的)项目! :)
    • 我们从哪里得到这个文件? C:\\truststore_client
    • Java 7 允许在一个块中捕获多个异常 xD
    【解决方案3】:

    要记住的是,此错误不仅是由于自签名证书造成的。新的 Entrust CA 证书失败并出现同样的错误,正确的做法是使用适当的根证书更新服务器,而不是禁用此重要的安全功能。

    【讨论】:

      【解决方案4】:

      看看这个:http://code.google.com/p/resting/。我可以用休息来消费 HTTPS REST 服务。

      【讨论】:

        【解决方案5】:

        delfuego的回答是解决证书问题最简单的方法。但是,就我而言,我们的第三方网址之一(使用 https)每 2 个月自动更新一次证书。这意味着我也必须每 2 个月手动将证书导入我们的 Java 信任库。有时它会导致生产问题。

        所以,我用 SecureRestClientTrustManager 解决了这个问题,以便能够在不导入证书文件的情况下使用 https url。 方法如下:

        public static String doPostSecureWithHeader(String url, String body, Map headers) 抛出异常 { log.info("开始 doPostSecureWithHeader " + url + " with param " + body); 长启动时间; 长结束时间; startTime = System.currentTimeMillis(); 客户端客户端; 客户端 = Client.create(); 网络资源网络资源; 网络资源=空; 字符串输出=空; 尝试{ SSLContext sslContext = null; SecureRestClientTrustManager secureRestClientTrustManager = new SecureRestClientTrustManager(); sslContext = SSLContext.getInstance("SSL"); ssl上下文 .init(空, 新的 javax.net.ssl.TrustManager[] { secureRestClientTrustManager }, 空值); DefaultClientConfig defaultClientConfig = new DefaultClientConfig(); 默认客户端配置 .getProperties() .put(com.sun.jersey.client.urlconnection.HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, 新的 com.sun.jersey.client.urlconnection.HTTPSProperties( getHostnameVerifier(), sslContext)); 客户端 = Client.create(defaultClientConfig); webResource = client.resource(url); if(headers!=null && headers.size()>0){ for (Map.Entry entry : headers.entrySet()){ webResource.setProperty(entry.getKey(), entry.getValue()); } } WebResource.Builder 构建器 = webResource.accept("应用程序/json"); if(headers!=null && headers.size()>0){ for (Map.Entry entry : headers.entrySet()){ builder.header(entry.getKey(), entry.getValue()); } } ClientResponse 响应 = 构建器 .post(ClientResponse.class, body); 输出 = response.getEntity(String.class); } 捕获(异常 e){ log.error(e.getMessage(),e); if(e.toString().contains("一个或多个查询值参数为空")){ 输出=“-1”; } if(e.toString().contains("401 Unauthorized")){ 扔 e; } } 最后 { 如果(客户端!= null){ 客户端.destroy(); } } endTime = System.currentTimeMillis(); log.info("时间命中 "+ url +" selama "+ (endTime - startTime) + " 毫秒 dengan output = "+output); 返回输出; }

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-06-13
          • 2016-07-21
          • 1970-01-01
          • 2014-01-31
          • 1970-01-01
          • 1970-01-01
          • 2014-09-14
          • 2017-05-09
          相关资源
          最近更新 更多