在Java中使用HTTPS和REST

在Java中使用HTTPS和REST,java,rest,ssl,x509,Java,Rest,Ssl,X509,我有一个用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(ResourceCo

我有一个用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)
从我在网上找到的例子来看,似乎我需要设置一个信任库,然后设置某种信任管理器。对于我这个简单的小项目来说,这似乎需要很多代码和设置工作。有没有更简单的方法说..我信任此证书并指向一个.cert文件?

当你说“有没有更简单的方法…信任此证书”时,这正是你通过将证书添加到Java信任库所做的。这是非常非常容易做到的,而且您无需在客户端应用程序中执行任何操作即可识别或利用该信任商店

在客户端计算机上,查找cacerts文件的位置(这是默认的Java信任存储,默认情况下位于/lib/security/certs/cacerts)

然后,键入以下内容:

keytool -import -alias <Name for the cert> -file <the .cer file> -keystore <path to cacerts>
keytool-import-alias-file-keystore
这将把证书导入到您的信任存储中,之后,您的客户端应用程序将能够毫无问题地连接到Grizzly HTTPS服务器

如果您不想将证书导入到默认的信任存储中——也就是说,您只希望它对这个客户端应用程序可用,而不是对在该机器上JVM上运行的任何其他应用程序可用——那么您可以仅为您的应用程序创建一个新的信任存储新信任存储文件的路径:

keytool -import -alias <Name for the cert> -file <the .cer file> -keystore <path to new trust store>
keytool-import-alias-file-keystore
将要求您为信任存储文件设置并验证新密码。然后,当您启动客户端应用程序时,请使用以下参数启动它:

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

真的很简单。

以下是痛苦的途径:

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

一定有更好的答案。

请查看以下内容:。我可以用休息来消耗
HTTPS REST服务。

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

delfuego的答案是解决证书问题的最简单方法。但是,在我的例子中,我们的一个第三方url(使用https)每2个月自动更新一次证书。这意味着我必须每两个月手动将证书导入我们的Java信任存储。有时会造成生产问题

因此,我用SecureRestClientTrustManager解决了这个问题,这样就可以在不导入证书文件的情况下使用https url。 方法如下:

public static String doPostSecureWithHeader(String url, String body, Map headers) throws Exception { log.info("start doPostSecureWithHeader " + url + " with param " + body); long startTime; long endTime; startTime = System.currentTimeMillis(); Client client; client = Client.create(); WebResource webResource; webResource = null; String output = null; try{ SSLContext sslContext = null; SecureRestClientTrustManager secureRestClientTrustManager = new SecureRestClientTrustManager(); sslContext = SSLContext.getInstance("SSL"); sslContext .init(null, new javax.net.ssl.TrustManager[] { secureRestClientTrustManager }, null); DefaultClientConfig defaultClientConfig = new DefaultClientConfig(); defaultClientConfig .getProperties() .put(com.sun.jersey.client.urlconnection.HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, new com.sun.jersey.client.urlconnection.HTTPSProperties( getHostnameVerifier(), sslContext)); client = 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 builder = webResource.accept("application/json"); if(headers!=null && headers.size()>0){ for (Map.Entry entry : headers.entrySet()){ builder.header(entry.getKey(), entry.getValue()); } } ClientResponse response = builder .post(ClientResponse.class, body); output = response.getEntity(String.class); } catch(Exception e){ log.error(e.getMessage(),e); if(e.toString().contains("One or more of query value parameters are null")){ output="-1"; } if(e.toString().contains("401 Unauthorized")){ throw e; } } finally { if (client!= null) { client.destroy(); } } endTime = System.currentTimeMillis(); log.info("time hit "+ url +" selama "+ (endTime - startTime) + " milliseconds dengan output = "+output); return output; } 公共静态字符串doPostSecureWithHeader(字符串url、字符串正文、映射头) 抛出异常{ log.info(“启动doPostSecureWithHeader”+url+”和参数“+body”); 长启动时间; 长时间; startTime=System.currentTimeMillis(); 客户; client=client.create(); 网络资源; webResource=null; 字符串输出=null; 试一试{ SSLContext SSLContext=null; SecureRestClientTrustManager SecureRestClientTrustManager=新SecureRestClientTrustManager(); sslContext=sslContext.getInstance(“SSL”); sslContext .init(空, 新的javax.net.ssl.TrustManager[]{secureRestClientTrustManager}, 无效); DefaultClientConfig DefaultClientConfig=新的DefaultClientConfig(); defaultClientConfig .getProperties() .put(com.sun.jersey.client.urlconnection.httpsprroperties.PROPERTIES\u HTTPS\u PROPERTIES, new com.sun.jersey.client.urlconnection.HTTPSProperties( getHostnameVerifier(),sslContext)); client=client.create(defaultClientConfig); webResource=client.resource(url); if(headers!=null&&headers.size()>0){ for(Map.Entry:headers.entrySet()){ setProperty(entry.getKey(),entry.getValue()); } } WebResource.Builder= accept(“application/json”); if(headers!=null&&headers.size()>0){ for(Map.Entry:headers.entrySet()){ builder.header(entry.getKey(),entry.getValue()); } } ClientResponse响应=builder .post(ClientResponse.class,body); 输出=response.getEntity(String.class); } 捕获(例外e){ log.error(e.getMessage(),e); if(例如toString()包含(“一个或多个查询值参数为null”)){ 输出=“-1”; } 如果(例如toString()包含(“401未经授权”)){ 投掷e; } } 最后{ 如果(客户端!=null){ client.destroy(); } } endTime=System.currentTimeMillis(); log.info(“时间命中”+url+“selama”+(endTime-startTime)+“毫秒数输出”=“+output”); 返回输出; }
很好,但是。。。我可以设置信任库吗