首頁>技術>

一、前言

上一篇已經完整的講解了spring boot 與cxf整合輸出日誌的示例工程,並針對線上生成環境提出了加解密及驗籤等安全技術。

本文將帶領大家實現Spring Boot 整合CXF實現數字證書籤名、SSL會話功能。

專案中涉及數字證書的製作,相關示例請參考:

數字簽名簡介 https://www.toutiao.com/i6930517389587218957/

專案原始碼: https://github.com/ouyushan/spring-webservice-samples

spring-cxf參考原始碼: https://github.com/code-not-found/cxf-jaxws

spring-ws參考原始碼: https://github.com/code-not-found/spring-ws

二、開發環境

IDE::IDEA

JDK:1.8

maven:3.6.2

spring boot:2.4.0

cxf:3.4.1

三、實現步驟

具體程式碼實現步驟和上篇一樣。本章在之前基礎上講解新增內容。

3.1、實現服務端和客戶端

新建服務端和客戶端spring-cxf-client-ssl、spring-cxf-server-ssl,並完成服務部署和客戶端訪問介面,具體步驟見上一篇。

3.2、新增SSL依賴

spring boot與cxf整合後要進行SSL會話只需引入cxf-rt-ws-security,並實現相關配置即可。

服務端、客服端pom檔案中新增cxf依賴包

<dependency>   <groupId>org.apache.cxf</groupId>   <artifactId>cxf-rt-ws-security</artifactId>   <version>3.4.1</version></dependency>
3.3、服務端啟用SSL

在服務端application.yml中啟用SSL,並指定證書資訊

server:  port: 8080  #  開啟SSL  ssl:    enabled: true    key-store: classpath:jks/webclient.p12    key-store-password: ouyushan.pass    key-alias: webclient    key-password: ouyushan.pass
3.4、客戶端配置SSL資訊

在客戶端application.yml中配置證書資訊,並在配置類中對SSLContext進行初始化配置

server:  port: 7070  serviceUrl: https://localhost:8080/ws/userclient:  keystore-alias: ouyushan.pass  key-store: classpath:jks/webclient.p12  trust-store: classpath:jks/webclient.p12  store-password: ouyushan.pass

CxfClientConfig.java

package org.ouyushan.cxf.config;import org.apache.cxf.configuration.jsse.TLSClientParameters;import org.apache.cxf.configuration.security.FiltersType;import org.apache.cxf.endpoint.Client;import org.apache.cxf.ext.logging.LoggingInInterceptor;import org.apache.cxf.ext.logging.LoggingOutInterceptor;import org.apache.cxf.frontend.ClientProxy;import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;import org.apache.cxf.transport.http.HTTPConduit;import org.apache.cxf.transport.https.httpclient.DefaultHostnameVerifier;import org.ouyushan.cxf.ws.UserService;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.Resource;import sun.security.util.HostnameChecker;import javax.net.ssl.KeyManager;import javax.net.ssl.KeyManagerFactory;import javax.net.ssl.TrustManager;import javax.net.ssl.TrustManagerFactory;import java.io.IOException;import java.io.InputStream;import java.security.KeyStore;import java.security.KeyStoreException;import java.security.NoSuchAlgorithmException;import java.security.UnrecoverableKeyException;import java.security.cert.CertificateException;/** * @Description: * @Author: ouyushan * @Email: ouyushan@hotmail.com * @Date: 2020/11/26 16:52 */@Configurationpublic class CxfClientConfig {    /**     * SSL配置引數     */    @Value("${client.key-store}")    private Resource keyStoreResource;    @Value("${client.trust-store}")    private Resource trustStoreResource;    @Value("${client.store-password}")    private String storePassword;    @Value("${server.serviceUrl}")    private String serviceUrl;    @Bean    public UserService userService() throws Exception {        JaxWsProxyFactoryBean jaxWsProxyFactoryBean =                new JaxWsProxyFactoryBean();        jaxWsProxyFactoryBean.setServiceClass(UserService.class);        jaxWsProxyFactoryBean.setAddress(serviceUrl);        // log the request and response messages        jaxWsProxyFactoryBean.getInInterceptors()                .add(loggingInInterceptor());        jaxWsProxyFactoryBean.getOutInterceptors()                .add(loggingOutInterceptor());        UserService userService = (UserService) jaxWsProxyFactoryBean.create();        return userService;    }    @Bean    public LoggingInInterceptor loggingInInterceptor() {        return new LoggingInInterceptor();    }    @Bean    public LoggingOutInterceptor loggingOutInterceptor() {        return new LoggingOutInterceptor();    }    /**     * SSL 相關配置     *     * @return     * @throws Exception     */    @Bean    public HTTPConduit httpConduit()            throws Exception {        Client client = ClientProxy.getClient(userService());        HTTPConduit httpConduit = (HTTPConduit) client.getConduit();        httpConduit.setTlsClientParameters(tlsClientParameters());        return httpConduit;    }    @Bean    public TLSClientParameters tlsClientParameters()            throws Exception {        TLSClientParameters tlsClientParameters =                new TLSClientParameters();        // 指定協議        tlsClientParameters.setSecureSocketProtocol("TLS");        // should NOT be used in production        tlsClientParameters.setDisableCNCheck(true);        // 設定信任證書(對方證書)        tlsClientParameters.setTrustManagers(trustManagers());        // 設定keystore(自己證書)        // tlsClientParameters.setKeyManagers(keyManagers());        tlsClientParameters.setCipherSuitesFilter(cipherSuitesFilter());        // 會覆蓋setDisableCNCheck 或自定義HostnameVerifier        //tlsClientParameters.setHostnameVerifier(new DefaultHostnameVerifier());        // 根據證書資訊獲取指定的域名資訊,等效於setHostnameVerifier(new DefaultHostnameVerifier());        //tlsClientParameters.setUseHttpsURLConnectionDefaultHostnameVerifier(true);        // 當證書資訊包含服務域名時可使用此配置        //tlsClientParameters.setUseHttpsURLConnectionDefaultSslSocketFactory(true);        return tlsClientParameters;    }    @Bean    public TrustManager[] trustManagers()            throws NoSuchAlgorithmException, KeyStoreException,            IOException {        TrustManagerFactory trustManagerFactory = TrustManagerFactory                .getInstance(TrustManagerFactory.getDefaultAlgorithm());        trustManagerFactory.init(trustStore());        return trustManagerFactory.getTrustManagers();    }    /**     * @return     * @throws NoSuchAlgorithmException     * @throws KeyStoreException     * @throws CertificateException     * @throws IOException     * @throws UnrecoverableKeyException     *//*    @Bean    public KeyManager[] keyManagers()            throws NoSuchAlgorithmException, KeyStoreException,            IOException, UnrecoverableKeyException {        KeyManagerFactory keyManagerFactory = KeyManagerFactory                .getInstance(KeyManagerFactory.getDefaultAlgorithm());        keyManagerFactory.init(keyStore(), storePassword.toCharArray());        return keyManagerFactory.getKeyManagers();    }*/    @Bean    public KeyStore trustStore() throws KeyStoreException, IOException {        KeyStore trustStore = KeyStore.getInstance("PKCS12");        InputStream inputStream = trustStoreResource.getInputStream();        try {            trustStore.load(inputStream,                    storePassword.toCharArray());        } catch (IOException e) {            e.printStackTrace();        } catch (NoSuchAlgorithmException e) {            e.printStackTrace();        } catch (CertificateException e) {            e.printStackTrace();        }        return trustStore;    }    @Bean    public KeyStore keyStore() throws KeyStoreException, IOException {        KeyStore trustStore = KeyStore.getInstance("PKCS12");        InputStream inputStream = keyStoreResource.getInputStream();        try {            trustStore.load(inputStream,                    storePassword.toCharArray());        } catch (IOException e) {            e.printStackTrace();        } catch (NoSuchAlgorithmException e) {            e.printStackTrace();        } catch (CertificateException e) {            e.printStackTrace();        }        return trustStore;    }    @Bean    public FiltersType cipherSuitesFilter() {                FiltersType filter = new FiltersType();        filter.getInclude().add("TLS_ECDHE_RSA_.*");        filter.getInclude().add("TLS_DHE_RSA_.*");        return filter;    }}
3.5、功能驗證

分別啟動服務端和客戶端,訪問客戶端請求介面:

http://localhost:7070/user/get?userId=1

客戶端控制檯輸出如下:

2020-12-03 16:57:26.434  INFO 1500 --- [nio-7070-exec-2] o.a.cxf.services.UserService.REQ_OUT     : REQ_OUT    Address: https://localhost:8080/ws/user    HttpMethod: POST    Content-Type: text/xml    ExchangeId: a2b51140-15a6-4982-a4e7-5375435ba482    ServiceName: UserServiceService    PortName: UserServicePort    PortTypeName: UserService    Headers: {SOAPAction="", Accept=*/*}    Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:getUserName xmlns:ns2="http://ws.cxf.ouyushan.org/"><userId>1</userId></ns2:getUserName></soap:Body></soap:Envelope>2020-12-03 16:57:26.488  INFO 1500 --- [nio-7070-exec-2] o.a.cxf.services.UserService.RESP_IN     : RESP_IN    Address: https://localhost:8080/ws/user    Content-Type: text/xml;charset=UTF-8    ResponseCode: 200    ExchangeId: a2b51140-15a6-4982-a4e7-5375435ba482    ServiceName: UserServiceService    PortName: UserServicePort    PortTypeName: UserService    Headers: {Keep-Alive=timeout=60, connection=keep-alive, content-type=text/xml;charset=UTF-8, Content-Length=220, Date=Thu, 03 Dec 2020 08:57:26 GMT}    Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:getUserNameResponse xmlns:ns2="http://ws.cxf.ouyushan.org/"><return>tom</return></ns2:getUserNameResponse></soap:Body></soap:Envelope>

服務端控制檯輸出如下:

2020-12-03 16:57:26.482  INFO 9588 --- [nio-8080-exec-5] o.a.cxf.services.UserService.REQ_IN      : REQ_IN    Address: https://localhost:8080/ws/user    HttpMethod: POST    Content-Type: text/xml; charset=UTF-8    ExchangeId: 23f1bff5-af63-4346-88ef-d1d5834266e3    ServiceName: UserService    PortName: UserServiceImplPort    PortTypeName: UserService    Headers: {SOAPAction="", Accept=*/*, host=localhost:8080, connection=keep-alive, content-type=text/xml; charset=UTF-8, cache-control=no-cache, Content-Length=202, pragma=no-cache, user-agent=Apache-CXF/3.4.1}    Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:getUserName xmlns:ns2="http://ws.cxf.ouyushan.org/"><userId>1</userId></ns2:getUserName></soap:Body></soap:Envelope>2020-12-03 16:57:26.483  INFO 9588 --- [nio-8080-exec-5] o.a.cxf.services.UserService.RESP_OUT    : RESP_OUT    Address: https://localhost:8080/ws/user    Content-Type: text/xml    ResponseCode: 200    ExchangeId: 23f1bff5-af63-4346-88ef-d1d5834266e3    ServiceName: UserService    PortName: UserServiceImplPort    PortTypeName: UserService    Headers: {}    Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:getUserNameResponse xmlns:ns2="http://ws.cxf.ouyushan.org/"><return>tom</return></ns2:getUserNameResponse></soap:Body></soap:Envelope>

從日誌可以看出完整的請求鏈路資訊,且服務端地址已變為

https://localhost:8080/ws/user

四、總結

透過本篇教材,您應該已掌握Spring Boot整合CXF實現SSL會話。整個過程可分為兩步:

服務端啟用SSL;客戶端配置SSL相關資訊,包括httpConduit和tlsClientParameters等。

需要注意的是,自制證書由於沒有指定CN為域名或ip,在配置客戶端時需要注意以下事宜:

// 會覆蓋setDisableCNCheck 或自定義HostnameVerifier//tlsClientParameters.setHostnameVerifier(new DefaultHostnameVerifier());// 根據證書資訊獲取指定的域名資訊,等效於setHostnameVerifier(new DefaultHostnameVerifier());//tlsClientParameters.setUseHttpsURLConnectionDefaultHostnameVerifier(true);// 當證書資訊包含服務域名時可使用此配置//tlsClientParameters.setUseHttpsURLConnectionDefaultSslSocketFactory(true);

至此Spring Boot 整合CXF實現SSL會話已整合完成,證書的製作以及服務的配置文中已詳細講解。下一步是在此教材的基礎上實現簽名功能。

13
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 探索 .NET Core的依賴注入