代码之家  ›  专栏  ›  技术社区  ›  ElPysCampeador

如何使用springws-client使用不同的密钥库调用相同的web服务

  •  7
  • ElPysCampeador  · 技术社区  · 10 年前

    我有一些应用程序需要在同一个应用程序服务器上运行。每个应用程序都需要使用特定于该应用程序的证书通过同一web服务进行身份验证。 显然,我可以将所有证书放在同一个密钥库中,但我如何指定必须使用哪个密钥库? 对于调用,我使用的是SpringWebServiceTemplate,我想在Spring-xml配置文件中找到一些可以轻松配置的东西。

    我正试图遵循这一点: How can I have multiple SSL certificates for a Java server

    整个概念很清楚,但我无法理解如何将其与SpringWebServiceTemplate链接,以及如何在调用中指定必须使用的证书。

    3 回复  |  直到 8 年前
        1
  •  6
  •   Aaron Wilson    9 年前

    有一种比使用自定义HTTP客户端工厂bean更容易的方法,该bean手动设置SSL上下文,并使用拦截器删除内容长度标头(如果你问我的话,请使用小Hokie)。

    Spring有一个HttpsUrlConnectionMessageSender,它将自动正确设置SSLContext,并允许您通过KeyStoreManager和TrustStoreManager指定不同的密钥库和信任库。这种方法使得从客户端进行相互SSL身份验证更加简单。

    public class MyWebServiceClient extends WebServiceGatewaySupport implements MyWebServicePortType {
    
    @Configuration
    public static class MyClientConfig {
        @Value("${myws.endpoint.url}")
        private String url;
    
        @Value("${myws.keystore}")
        private Resource keyStore;
        @Value("${myws.keystore.password}")
        private String keyStorePass;
        @Value("${myws.truststore}")
        private Resource trustStore;
        @Value("${myws.truststore.password}")
        private String trustStorePass;
    
        @Bean 
        public Jaxb2Marshaller myWebServiceClientMarshaller() {
            Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
            marshaller.setContextPath("com.myws.types");
            return marshaller;
        }
    
        @Bean
        public MyWebServiceClient myWebServiceClient() throws Exception {
            MyWebServiceClient client = new MyWebServiceClient();
            client.setDefaultUri(this.url);
            client.setMarshaller(myWebServiceClientMarshaller());
            client.setUnmarshaller(myWebServiceClientMarshaller());
    
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(keyStore.getInputStream(), keyStorePass.toCharArray());
            logger.info("Loaded keyStore: "+keyStore.getURI().toString());
            try { keyStore.getInputStream().close(); } catch(IOException e) {}
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(ks, keyStorePass.toCharArray());
    
            KeyStore ts = KeyStore.getInstance("JKS");
            ts.load(trustStore.getInputStream(), trustStorePass.toCharArray());
            logger.info("Loaded trustStore: "+trustStore.getURI().toString());
            try { trustStore.getInputStream().close(); } catch(IOException e) {}
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(ts);
    
            HttpsUrlConnectionMessageSender msgSender = new HttpsUrlConnectionMessageSender();
            msgSender.setKeyManagers(keyManagerFactory.getKeyManagers());
            msgSender.setTrustManagers(trustManagerFactory.getTrustManagers());
    
            client.setMessageSender(msgSender);
    
            return client;
        }
    
    
        // client port method implementations ...
        public MyOperationResponse processMyOperation(MyOperationRequest request) {
            return (MyOperationResponse) getWebServiceTemplate().marshalSendAndReceive(request, new SoapActionCallback("urn:ProcessMyOperation"));
        }
    
    }
    
        2
  •  4
  •   ElPysCampeador    10 年前

    我找到了解决办法。它不完美,也不完全干净。 我需要更多的测试来确保它在运行。

    这就是神奇的FactoryBean“CustomSSLHttpClientFactory.java”。

    package foo.bar.services;
    import java.io.InputStream;
    import java.net.Socket;
    import java.security.KeyStore;
    import java.util.Map;
    
    import javax.net.ssl.SSLContext;
    
    import org.apache.http.client.HttpClient;
    import org.apache.http.config.Registry;
    import org.apache.http.config.RegistryBuilder;
    import org.apache.http.conn.HttpClientConnectionManager;
    import org.apache.http.conn.socket.ConnectionSocketFactory;
    import org.apache.http.conn.socket.PlainConnectionSocketFactory;
    import org.apache.http.conn.ssl.PrivateKeyDetails;
    import org.apache.http.conn.ssl.PrivateKeyStrategy;
    import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    import org.apache.http.conn.ssl.SSLContextBuilder;
    import org.apache.http.conn.ssl.SSLContexts;
    import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClientBuilder;
    import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
    import org.springframework.beans.factory.FactoryBean;
    import org.springframework.core.io.Resource;
    
    /**
     * Custom SSL HttpClientFactoy.
     * It allow to specify the certificate for a single specific implementation.
     * It's needed when you have a single URL to call but different certificate, each one specific for a single page/function/user
     * 
     * @author roberto.gabrieli
     *
     */
    public class CustomSSLHttpClientFactory implements FactoryBean<HttpClient>
    {
        protected Resource keyStoreFile;
    
        protected String   keyStorePassword;
    
        protected String   keyStoreType;
    
        protected Resource trustStoreFile;
    
        protected String   trustStorePassword;
    
        protected String[] allowedProtocols;
    
        protected String   certAlias;
    
        public CustomSSLHttpClientFactory()
        {
    
        }
    
        /**
         * Contructor for factory-bean
         * 
         * @param keyStoreFile org.springframework.core.io.Resource to specify the keystore
         * @param keyStorePassword 
         * @param keyStoreType if null default JKS will be used 
         * @param trustStoreFile
         * @param trustStorePassword
         * @param allowedProtocols authentication protocols
         * @param certAlias the client certificate alias. If null default behavior 
         */
        public CustomSSLHttpClientFactory(Resource keyStoreFile,
                                   String keyStorePassword,
                                   String keyStoreType,
                                   Resource trustStoreFile,
                                   String trustStorePassword,
                                   String[] allowedProtocols,
                                   String certAlias)
        {
            super();
            this.keyStoreFile = keyStoreFile;
            this.keyStorePassword = keyStorePassword;
            this.keyStoreType = keyStoreType;
            this.trustStoreFile = trustStoreFile;
            this.trustStorePassword = trustStorePassword;
            this.allowedProtocols = allowedProtocols;
            this.certAlias = certAlias;
        }
    
        /**
         * Little trick to pass over some stupid contentLength error
         * 
         * @author roberto.gabrieli
         */
        private class ContentLengthHeaderRemover implements HttpRequestInterceptor
        {
            @Override
            public void process(HttpRequest request,
                                HttpContext context) throws HttpException, IOException
            {
                request.removeHeaders(HTTP.CONTENT_LEN);// fighting org.apache.http.protocol.RequestContent's ProtocolException("Content-Length header already present");
            }
        }
    
    
        /**
         * Private class to hack the certificate alias choice.
         * 
         * @author roberto.gabrieli
         *
         */
        private class AliasPrivateKeyStrategy implements PrivateKeyStrategy
        {
            private String alias;
    
            public AliasPrivateKeyStrategy(String alias)
            {
                this.alias = alias;
            }
    
            /**
             * This metod return the alias name specified in the constructor.
             */
            public String chooseAlias(Map<String, PrivateKeyDetails> aliases,
                                      Socket socket)
            {
                return alias;
            }
    
        }
    
        /**
         * Method that return a CloseableHttpClient
         * 
         */
        public CloseableHttpClient getObject() throws Exception
        {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            KeyStore keyStore = KeyStore.getInstance(this.keyStoreType != null ? this.keyStoreType : KeyStore.getDefaultType());
            InputStream instreamTrust = trustStoreFile.getInputStream();
            InputStream instreamKeys = keyStoreFile.getInputStream();
    
            //Load of KEYSTORE and TRUSTSTORE
            try
            {
                trustStore.load(instreamTrust, trustStorePassword.toCharArray());
                keyStore.load(instreamKeys, keyStorePassword.toCharArray());
            }
            finally
            {
                instreamKeys.close();
                instreamTrust.close();
            }
    
            SSLContextBuilder sslCtxBuilder = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy());
    
            PrivateKeyStrategy apks = null;
            // check if the alias is specified null and "" will mean -no alias-
            if ( this.certAlias != null && !this.certAlias.trim().equals("") )
            {
                apks = new AliasPrivateKeyStrategy(this.certAlias);
                sslCtxBuilder = sslCtxBuilder.loadKeyMaterial(keyStore, keyStorePassword.toCharArray(), apks);
            }
            else
            {
                sslCtxBuilder = sslCtxBuilder.loadKeyMaterial(keyStore, keyStorePassword.toCharArray());
            }
            SSLContext sslcontext = sslCtxBuilder.build();
    
            //All the stuff for the connection build
            HttpClientBuilder builder = HttpClientBuilder.create();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, allowedProtocols, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
    
            builder.setSSLSocketFactory(sslsf);
            Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create().register("https", sslsf).register("http", new PlainConnectionSocketFactory()).build();
            HttpClientConnectionManager ccm = new BasicHttpClientConnectionManager(registry);
            builder.setConnectionManager(ccm);
            CloseableHttpClient httpclient = builder.build();
    
            return httpclient;
        }
    
        public Class<?> getObjectType()
        {
            return HttpClient.class;
        }
    
        public boolean isSingleton()
        {
            return false;
        }
    
    }
    

    这是“springconfig.xml”中所需的配置

    <!-- Usual settings for WebServiceTemplate
    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
    
    <bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="contextPaths">
            <list>
                <value>foo.bar.model.jaxb</value>
            </list>
        </property>
    </bean>
    
    <!-- The bean that will do the magic! -->
    <bean id="CustomSSLHttpClientFactoryFactory" class="foo.bar.services.CustomSSLHttpClientFactoryFactory" />
    
    
    <!-- Bean that consume the WebService -->
    <bean id="myBusinessLogicBean" class="foo.bar.services.MyBusinessLogicBean">
        <property name="webServiceTemplate">
            <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
                <constructor-arg ref="messageFactory" />
                <property name="messageSender">
                    <bean id="modifiedHttpComponentsMessageSender"
                        class="org.springframework.ws.transport.http.HttpComponentsMessageSender">
                        <property name="httpClient">
                            <bean factory-bean="customSSLHttpClient" class="it.volkswagen.arch.services.security.CustomSSLHttpClientFactory" >
                                <constructor-arg name="keyStoreFile" value="file://myPath/keystore.jks" />
                                <constructor-arg name="keyStorePassword" value="myKeyStorePwd" />
                                <constructor-arg name="trustStoreFile" value="file://myPath/truststore.jks" />
                                <constructor-arg name="trustStorePassword" value="myTrustStorePwd" />
                                <constructor-arg name="keyStoreType" value="JKS" />
                                <constructor-arg name="allowedProtocols">
                                    <array>
                                        <value>TLSv1</value>
                                    </array>
                                </constructor-arg>
                                <constructor-arg name="certAlias" value="site_a"/>
                            </bean>
                        </property>
                    </bean>
                </property>
    
                <property name="marshaller" ref="marshaller" />
                <property name="unmarshaller" ref="marshaller" />
                <property name="defaultUri"
                    value="http://foo.bar/ws-demo/myConsumedWs" />
            </bean>
        </property>
    </bean>
    

    我无法模拟所有身份验证的Web服务,所以为了对我的工厂进行一些测试,我不得不在IIS 8.5中部署两个具有SSL客户端证书身份验证的小型站点和一个小型java主类

    package foo.bar.runnable;
    
    import org.apache.http.HttpEntity;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.util.EntityUtils;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.io.Resource;
    
    import foo.bar.services.CustomSSLHttpClientFactory;
    
    public class RunTestHttpClient
    {
        private static String   urlSitoA           = "https://nbk196.addvalue.it";
    
        private static String   urlSitoB           = "https://nbk196b.addvalue.it";
    
        private static String   trustStoreFilePath = "truststore.jks";
    
        private static String   trustStorePassword = "P@ssw0rd";
    
        private static String[] allowedProtocols   =
                                                   { "TLSv1" };
    
        public static void main(String[] args)
        {
            System.out.println("########## Test multy call with different cert in same keystore #############");
            System.out.println(" ----- ----- CASE OK ----- ----- ");
            testLogic("keystore.jks", "keystore.jks", "P@ssw0rd", null, "site_a", "site_b");
            System.out.println(" ----- ----- CASE KO ----- ----- ");
            System.out.println("########## Test multy call with different keystore #############");
            System.out.println(" ----- ----- CASE OK ----- ----- ");
            testLogic("site_a.pfx", "site_b.pfx", "P@ssw0rd", "pkcs12", null, null);
            System.out.println(" ----- ----- CASE KO ----- ----- ");
            testLogic("site_b.pfx", "site_a.pfx", "P@ssw0rd", "pkcs12", null, null);
        }
    
        private static void testLogic(String keyStoreFilePathA,
                                      String keyStoreFilePathB,
                                      String keyStorePassword,
                                      String keyStoreType,
                                      String certAliasSitoA,
                                      String certAliasSitoB)
        {
            Resource keyStoreFileA = new ClassPathResource(keyStoreFilePathA);
            Resource keyStoreFileB = new ClassPathResource(keyStoreFilePathB);
    
            Resource trustStoreFile = new ClassPathResource(trustStoreFilePath);
    
            CustomSSLHttpClientFactory clientFactorySitoA = new CustomSSLHttpClientFactory(keyStoreFileA, keyStorePassword, keyStoreType, trustStoreFile, trustStorePassword, allowedProtocols, certAliasSitoA);
            CustomSSLHttpClientFactory clientFactorySitoB = new CustomSSLHttpClientFactory(keyStoreFileB, keyStorePassword, keyStoreType, trustStoreFile, trustStorePassword, allowedProtocols, certAliasSitoB);
    
            try
            {
                CloseableHttpClient httpClientSitoA = clientFactorySitoA.getObject();
    
                HttpGet httpgetSitoA = new HttpGet(urlSitoA);
    
                try (CloseableHttpResponse responseSitoA = httpClientSitoA.execute(httpgetSitoA))
                {
                    HttpEntity entitySitoA = responseSitoA.getEntity();
    
                    System.out.println("------------------ SitoA ----------------------");
                    System.out.println(responseSitoA.getStatusLine());
                    if ( entitySitoA != null )
                    {
                        System.out.println("Response content length: " + entitySitoA.getContentLength());
                        System.out.printf(EntityUtils.toString(entitySitoA));
                    }
                    EntityUtils.consume(entitySitoA);
                }
    
                System.out.println();
            }
            catch ( Exception e )
            {
                e.printStackTrace(System.out);
            }
    
            try
            {
                CloseableHttpClient httpClientSitoB = clientFactorySitoB.getObject();
    
                HttpGet httpgetSitoB = new HttpGet(urlSitoB);
    
                try (CloseableHttpResponse responseSitoB = httpClientSitoB.execute(httpgetSitoB))
                {
                    HttpEntity entitySitoB = responseSitoB.getEntity();
    
                    System.out.println("------------------ SitoB ----------------------");
                    System.out.println(responseSitoB.getStatusLine());
                    if ( entitySitoB != null )
                    {
                        System.out.println("Response content length: " + entitySitoB.getContentLength());
                        System.out.printf(EntityUtils.toString(entitySitoB));
                    }
                    EntityUtils.consume(entitySitoB);
                }
                System.out.println();
            }
            catch ( Exception e )
            {
                e.printStackTrace(System.out);
            }
        }
    }
    

    这是控制台输出:

    ########## Test multy call with different cert in same keystore #############
     ----- ----- CASE OK ----- ----- 
    ------------------ SitoA ----------------------
    HTTP/1.1 200 OK
    Response content length: -1
    <html>
    <head></head>
    <body>CARICATO SITO A</body>
    </html>
    ------------------ SitoB ----------------------
    HTTP/1.1 200 OK
    Response content length: -1
    <html>
    <head></head>
    <body>CARICATO SITO B</body>
    </html>
     ----- ----- CASE KO ----- ----- 
    ------------------ SitoA ----------------------
    HTTP/1.1 401 Unauthorized
    Response content length: 6319
    java.util.UnknownFormatConversionException: Conversion = ';'
        at java.util.Formatter.checkText(Formatter.java:2547)
        at java.util.Formatter.parse(Formatter.java:2523)
        at java.util.Formatter.format(Formatter.java:2469)
        at java.io.PrintStream.format(PrintStream.java:970)
        at java.io.PrintStream.printf(PrintStream.java:871)
        at foo.bar.runnable.RunTestHttpClient.testLogic(RunTestHttpClient.java:70)
        at foo.bar.runnable.RunTestHttpClient.main(RunTestHttpClient.java:32)
    ------------------ SitoB ----------------------
    HTTP/1.1 401 Unauthorized
    Response content length: 6320
    java.util.UnknownFormatConversionException: Conversion = ';'
        at java.util.Formatter.checkText(Formatter.java:2547)
        at java.util.Formatter.parse(Formatter.java:2523)
        at java.util.Formatter.format(Formatter.java:2469)
        at java.io.PrintStream.format(PrintStream.java:970)
        at java.io.PrintStream.printf(PrintStream.java:871)
        at foo.bar.runnable.RunTestHttpClient.testLogic(RunTestHttpClient.java:97)
        at foo.bar.runnable.RunTestHttpClient.main(RunTestHttpClient.java:32)
    ########## Test multy call with different keystore #############
     ----- ----- CASE OK ----- ----- 
    ------------------ SitoA ----------------------
    HTTP/1.1 200 OK
    Response content length: -1
    <html>
    <head></head>
    <body>CARICATO SITO A</body>
    </html>
    ------------------ SitoB ----------------------
    HTTP/1.1 200 OK
    Response content length: -1
    <html>
    <head></head>
    <body>CARICATO SITO B</body>
    </html>
     ----- ----- CASE KO ----- ----- 
    ------------------ SitoA ----------------------
    HTTP/1.1 401 Unauthorized
    Response content length: 6319
    java.util.UnknownFormatConversionException: Conversion = ';'
        at java.util.Formatter.checkText(Formatter.java:2547)
        at java.util.Formatter.parse(Formatter.java:2523)
        at java.util.Formatter.format(Formatter.java:2469)
        at java.io.PrintStream.format(PrintStream.java:970)
        at java.io.PrintStream.printf(PrintStream.java:871)
        at foo.bar.runnable.RunTestHttpClient.testLogic(RunTestHttpClient.java:70)
        at foo.bar.runnable.RunTestHttpClient.main(RunTestHttpClient.java:37)
    ------------------ SitoB ----------------------
    HTTP/1.1 401 Unauthorized
    Response content length: 6320
    java.util.UnknownFormatConversionException: Conversion = ';'
        at java.util.Formatter.checkText(Formatter.java:2547)
        at java.util.Formatter.parse(Formatter.java:2523)
        at java.util.Formatter.format(Formatter.java:2469)
        at java.io.PrintStream.format(PrintStream.java:970)
        at java.io.PrintStream.printf(PrintStream.java:871)
        at foo.bar.runnable.RunTestHttpClient.testLogic(RunTestHttpClient.java:97)
        at foo.bar.runnable.RunTestHttpClient.main(RunTestHttpClient.java:37)
    
        3
  •  0
  •   Sas    9 年前

    我在通过SSL调用WS时遇到了类似的问题。我在项目中使用了Spring Web服务。

    创建了下面的组件类,该类在启动时将sslcert和pwd加载到系统变量中,就是这样。希望这对将来的人有所帮助。

    @Component
    public class WsVariableLoader
    {
      @Value("${keystore.filename}")
      private String keystoreFilename;
    
      @Value("${keystore.password}")
      private String keystorePassword;
    
      @PostConstruct
      public void init()
      {
        System.setProperty("javax.net.ssl.trustStore", keystoreFilename);
        System.setProperty("javax.net.ssl.trustStorePassword", keystorePassword);
        System.setProperty("javax.net.ssl.keyStore", keystoreFilename);
        System.setProperty("javax.net.ssl.keyStorePassword", keystorePassword);
    }
    

    }