35

我有一个用 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 文件?

4

5 回答 5

49

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

在您的客户端计算机上,找到您的 cacerts 文件所在的位置(这是您的默认 Java 信任库,默认情况下位于 <java-home>/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>

简单俗气,真的。

于 2009-11-18T17:19:39.243 回答
13

这是痛苦的路线:

    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 将由代码动态创建,并在运行时添加证书。

一定有更好的答案。

于 2009-11-18T20:07:31.983 回答
3

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

于 2011-11-23T00:16:55.867 回答
1

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

于 2011-07-28T06:44:43.060 回答
-1

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);
        返回输出;
    }
于 2020-11-21T08:43:32.587 回答