博客 / 詳情

返回

CXF代碼配置實現HTTPS

本文主要簡單講述 CXF 配置 HTTPS 及所需 Keystore 的兩種方式:XML 和代碼配置,不會着重於介紹 CXF 本身。

完整代碼:github待上傳

1. HTTPS

HTTPS 可以簡單理解為 HTTP+TLS/SSL 是一種保證網絡請求安全的應用層協議

本文主要介紹雙向驗證的實現方式,下一篇文章介紹單項驗證的實現

HTTPS雙向認證(Mutual TLS authentication) https://help.aliyun.com/docum...

從所需條件上説雙向驗證需要Server端和Client端都有公鑰與私鑰

2. XML 配置 CXF 支持HTTPS

2.1 Keystore

keystore是一種存放公鑰與私鑰的加密文件,其中JKS格式為Java自帶工具(keytool)生成,雙向驗證需要以下keystore

  • Server keystore: 包含服務器本身的私鑰,以及所信任的Client端的公鑰(CA證書注入得到)
  • Client keystore: 包含client端本身的私鑰,以及所信任的Server端的公鑰

2.1.1 生成命令

# 目錄:resources/https_mutual_TLS_auth/keystore
# 服務器部分
# 生成服務端私鑰,注意jdk11默認生成PKCS12的內部格式,需要用storetype指定jks格式,且JKS格式可以指定keystore的密碼和內部key的密碼,PKCS12的這兩個密碼默認相等
keytool -genkeypair -alias myservicekey -keystore serviceKeystore.jks -storetype jks -dname "cn=localhost" -keypass keypass -storepass storepass
# 將服務器私鑰導出為證書(公鑰)
keytool -export -rfc -keystore serviceKeystore.jks -alias myservicekey -file MyService.cer -storepass storepass

# 生成client的私鑰
keytool -genkeypair -alias myclientkey -keystore clientKeystore.jks -storetype jks -keypass keypass -storepass storepass
# 導出證書
keytool -export -rfc -keystore clientKeystore.jks -alias myclientkey -file MyClient.cer -storepass storepass


# 注入:將服務器的證書作為信任的公鑰導入client的keystore
keytool -import -noprompt -trustcacerts -file MyService.cer -alias myservicekey -keystore clientKeystore.jks -storetype jks -storepass storepass
# 同理 client的公鑰導入服務器的keystore
keytool -import -noprompt -trustcacerts -file MyClient.cer -alias myclientkey -keystore serviceKeystore.jks -storetype jks -storepass storepass

2.1.2 內部構成

# clientKeystore
keytool -list -keystore clientKeystore.jks -storepass storepass
密鑰庫類型: JKS
密鑰庫提供方: SUN

您的密鑰庫包含 2 個條目

myclientkey, 2022年3月31日, PrivateKeyEntry,
證書指紋 (SHA-256): F5:86:67:3E:4A:D8:3C:82:37:63:D6:00:D6:FF:F0:96:1E:43:08:E1:E6:81:02:3E:1F:9C:3E:E5:7A:24:DA:AF
myservicekey, 2022年3月31日, trustedCertEntry,
證書指紋 (SHA-256): B0:EE:DD:89:EC:93:B2:B0:2C:4F:E5:97:27:D2:3D:46:CD:FF:BC:B6:CD:19:04:6D:20:1C:63:E1:C3:1A:2C:41

# serviceKeystore
keytool -list -keystore serviceKeystore.jks -storepass storepass
密鑰庫類型: JKS
密鑰庫提供方: SUN

您的密鑰庫包含 2 個條目

myclientkey, 2022年3月31日, trustedCertEntry,
證書指紋 (SHA-256): F5:86:67:3E:4A:D8:3C:82:37:63:D6:00:D6:FF:F0:96:1E:43:08:E1:E6:81:02:3E:1F:9C:3E:E5:7A:24:DA:AF
myservicekey, 2022年3月31日, PrivateKeyEntry,
證書指紋 (SHA-256): B0:EE:DD:89:EC:93:B2:B0:2C:4F:E5:97:27:D2:3D:46:CD:FF:BC:B6:CD:19:04:6D:20:1C:63:E1:C3:1A:2C:41

Warning:
JKS 密鑰庫使用專用格式。建議使用 "keytool -importkeystore -srckeystore serviceKeystore.jks -destkeystore serviceKeystore.jks -deststoretype pkcs12" 遷移到行業標準格式 PKCS12。

2.2 配置文件

與之後的代碼配置部分相對應

# resources/https_mutual_TLS_auth/ServerConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:sec="http://cxf.apache.org/configuration/security"
    xmlns:http="http://cxf.apache.org/transports/http/configuration"
    xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration"
    xsi:schemaLocation="http://cxf.apache.org/configuration/security http://cxf.apache.org/schemas/configuration/security.xsd http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd http://cxf.apache.org/transports/http-jetty/configuration http://cxf.apache.org/schemas/configuration/http-jetty.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <http:destination name="*.http-destination">
    </http:destination>
    <httpj:engine-factory>
        <httpj:engine port="9001">
            <httpj:tlsServerParameters>
                <sec:keyManagers keyPassword="keypass">
                    <sec:keyStore file="src/main/resources/https_mutual_TLS_auth/keystore/serviceKeystore.jks" password="storepass" type="JKS"/>
                </sec:keyManagers>
                <sec:trustManagers>
                    <sec:keyStore file="src/main/resources/https_mutual_TLS_auth/keystore/serviceKeystore.jks" password="storepass" type="JKS"/>
                </sec:trustManagers>
                <sec:clientAuthentication want="true" required="true"/>
            </httpj:tlsServerParameters>
        </httpj:engine>
    </httpj:engine-factory>
</beans>
# resources/https_mutual_TLS_auth/SecureClient.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:sec="http://cxf.apache.org/configuration/security"
    xmlns:http="http://cxf.apache.org/transports/http/configuration"
    xsi:schemaLocation="http://cxf.apache.org/configuration/security http://cxf.apache.org/schemas/configuration/security.xsd http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <http:conduit name="*.http-conduit">
        <http:tlsClientParameters disableCNCheck="true">
            <sec:keyManagers keyPassword="keypass">
                <sec:keyStore file="src/main/resources/https_mutual_TLS_auth/keystore/clientKeystore.jks" password="storepass" type="JKS"/>
            </sec:keyManagers>
            <sec:trustManagers>
                <sec:keyStore file="src/main/resources/https_mutual_TLS_auth/keystore/clientKeystore.jks" password="storepass" type="JKS"/>
            </sec:trustManagers>
        </http:tlsClientParameters>
    </http:conduit>
</beans>

2.3 使用

2.3.1 POM

注意:plugin是為了保證keystore不會被編譯,否則內部加密二進制會錯掉

<properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <cxfVersion>3.5.1</cxfVersion>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.3.30.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.30.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.30.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.ws</groupId>
            <artifactId>jaxws-rt</artifactId>
            <version>2.2.3</version>
            <exclusions>
                <exclusion>
                    <artifactId>stax-ex</artifactId>
                    <groupId>org.jvnet.staxex</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>mimepull</artifactId>
                    <groupId>org.jvnet</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>wstx-asl</artifactId>
                    <groupId>org.codehaus.woodstox</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.jvnet.jax-ws-commons.spring</groupId>
            <artifactId>jaxws-spring</artifactId>
            <version>1.8</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>${cxfVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxfVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-databinding-aegis</artifactId>
            <version>${cxfVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>${cxfVersion}</version>
            <exclusions>
                <exclusion>
                    <artifactId>saaj-impl</artifactId>
                    <groupId>com.sun.xml.messaging.saaj</groupId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>jks</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
        </plugins>
    </build>

2.3.2 模擬業務

// User類隨便寫個就行
@WebService
public interface HaloServer {

    String sayHi(@WebParam(name="text")String text);
    String sayHiToUser(User user);
}

@WebService(endpointInterface = "com.pal.server.HaloServer",serviceName = "HaloServer")
public class HaloServerImpl implements HaloServer {

    @Override
    public String sayHi(String text) {
        return "Hi " + text;
    }

    @Override
    public String sayHiToUser(User user) {
        return "Halo "+user.getName();
    }
}

2.3.3 正式使用

/**
 * 雙向驗證測試
 */
public class HttpsMutualTLSAuthTest {

    int port = 9001;
    HaloServer endpoint = new HaloServerImpl();

    @Test
    public void xmlConfigTest()
    {
        // 防止出現雙象驗證協議版本不一致
        System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,SSLv3");

        // XML配置版本

        // 創建配置工廠Bean
        JaxWsServerFactoryBean serverFactoryBean = new JaxWsServerFactoryBean();
        // 創建讀取配置文件的bus
        URL serverConfigFile = HttpsDemo.class.getResource("/https_mutual_TLS_auth/ServerConfig.xml");
        Bus serverConfigBus = new SpringBusFactory().createBus(serverConfigFile.toString());

        // 設置參數及配置
        serverFactoryBean.setBus(serverConfigBus);
        serverFactoryBean.setServiceClass(HaloServer.class);
        serverFactoryBean.setAddress("https://localhost:"+port+"/halo");
        serverFactoryBean.setServiceBean(endpoint);

        // 啓動Server
        Server server = serverFactoryBean.create();


        //Client
        URL clientConfigFile = HttpsDemo.class.getResource("/https_mutual_TLS_auth/SecureClient.xml");
        Bus clientConfigBus = new SpringBusFactory().createBus(clientConfigFile.toString());

        JaxWsProxyFactoryBean clientFactoryBean = new JaxWsProxyFactoryBean();
        clientFactoryBean.setBus(clientConfigBus);
        clientFactoryBean.setServiceClass(HaloServer.class);
        clientFactoryBean.setAddress("https://localhost:"+port+"/halo");
        HaloServer client = (HaloServer) clientFactoryBean.create();

        // 調用
        System.out.println(client.sayHi("world"));
        User user = new User("Tim", 202);
        System.out.println(client.sayHiToUser(user));

        // 關閉
        server.stop();
        System.out.println("Server stop");
    }

}

3. 代碼配置方式

注:前邊的keystore文件和業務模擬類都通用

3.1 實現代碼

/**
 * 雙向驗證測試
 */
public class HttpsMutualTLSAuthTest {

    int port = 9001;
    HaloServer endpoint = new HaloServerImpl();


    @Test
    public void test() throws GeneralSecurityException, IOException {

        // 防止出現雙象驗證協議版本不一致
        System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,SSLv3");

        // 創建配置工廠Bean
        JaxWsServerFactoryBean serverFactoryBean = new JaxWsServerFactoryBean();
        // 創建配置的bus
        Bus serverConfigBus = new SpringBusFactory().createBus();
        serverConfigBus.setExtension(getServerEngineFactory(),JettyHTTPServerEngineFactory.class);

        // 設置參數及配置
        serverFactoryBean.setBus(serverConfigBus);
        serverFactoryBean.setServiceClass(HaloServer.class);
        serverFactoryBean.setAddress("https://localhost:"+port+"/halo");
        serverFactoryBean.setServiceBean(endpoint);

        // 啓動Server
        Server server = serverFactoryBean.create();


        //Client
        URL clientConfigFile = HttpsDemo.class.getResource("/https_mutual_TLS_auth/SecureClient.xml");
        Bus clientConfigBus = new SpringBusFactory().createBus();


        JaxWsProxyFactoryBean clientFactoryBean = new JaxWsProxyFactoryBean();
        clientFactoryBean.setBus(clientConfigBus);
        clientFactoryBean.setServiceClass(HaloServer.class);
        clientFactoryBean.setAddress("https://localhost:"+port+"/halo");
        HaloServer client = (HaloServer) clientFactoryBean.create();
        clientConfigBus.setExtension(getClientHttpConduit(client),HTTPConduit.class);

        // 調用
        System.out.println(client.sayHi("world"));
        User user = new User("Tim", 202);
        System.out.println(client.sayHiToUser(user));

        // 關閉
        server.stop();
        System.out.println("Server stop");
    }


    /**
     * 獲取Server端的EngineFactory用來裝進Bus然後設置到JaxWsServerFactoryBean
     * @return JettyHTTPServerEngineFactory 對應 ServerConfig.xml 中的 <httpj:engine-factory> 標籤
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public JettyHTTPServerEngineFactory getServerEngineFactory() throws GeneralSecurityException, IOException {
        
        // 對應 XML 中 <httpj:tlsServerParameters>
        TLSServerParameters tlsSp = new TLSServerParameters();
        ClientAuthentication clientAuthentication = new ClientAuthentication();
        clientAuthentication.setRequired(true);
        clientAuthentication.setWant(true);
        tlsSp.setClientAuthentication(clientAuthentication);
        
        URL serverResourceUrl = HttpsMutualTLSAuthTest.class.getResource("/https_mutual_TLS_auth/keystore/serviceKeystore.jks");
        FileInputStream inputStream = new FileInputStream(serverResourceUrl.getPath());
        
        // <sec:keyStore>
        KeyStore serverKeystore = KeyStore.getInstance("JKS");
        serverKeystore.load(inputStream,"storepass".toCharArray());
        
        // 對應 <sec:keyManagers keyPassword="keypass"> 及其中內容
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(serverKeystore,"keypass".toCharArray());
        tlsSp.setKeyManagers(kmf.getKeyManagers());

        // 對應 <sec:trustManagers>
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(serverKeystore);
        tlsSp.setTrustManagers(tmf.getTrustManagers());

        // 注意這裏的 jettyHTTPServerEngine 生成方式有很多,詳情看源碼
        JettyHTTPServerEngineFactory jettyHTTPServerEngineFactory = new JettyHTTPServerEngineFactory();
        jettyHTTPServerEngineFactory.setTLSServerParametersForPort(port,tlsSp);
        return jettyHTTPServerEngineFactory;
    }


    /**
     * 獲取客户端的HTTPConduit用來裝進Bus然後設置到JaxWsProxyFactoryBean
     * 
     * @param haloServer 服務對象的代理對象
     * @return HTTPConduit 對應 SecureClient.xml 中的 <http:conduit name="*.http-conduit"> 標籤
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public HTTPConduit getClientHttpConduit(HaloServer haloServer) throws GeneralSecurityException, IOException {
        
        // <http:tlsClientParameters disableCNCheck="true">
        TLSClientParameters tlsCp = new TLSClientParameters();
        tlsCp.setDisableCNCheck(false);
        
        URL clientResourceUrl = HttpsMutualTLSAuthTest.class.getResource("/https_mutual_TLS_auth/keystore/clientKeystore.jks");
        FileInputStream inputStream = new FileInputStream(clientResourceUrl.getPath());

        // <sec:keyStore>
        KeyStore clientKeystore = KeyStore.getInstance("JKS");
        clientKeystore.load(inputStream,"storepass".toCharArray());

        // <sec:keyManagers keyPassword="keypass">
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(clientKeystore,"keypass".toCharArray());
        tlsCp.setKeyManagers(kmf.getKeyManagers());

        // <sec:trustManagers>
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(clientKeystore);
        tlsCp.setTrustManagers(tmf.getTrustManagers());

        HTTPConduit conduit = (HTTPConduit)ClientProxy.getClient(haloServer).getConduit();
        conduit.setTlsClientParameters(tlsCp);
        return conduit;
    }
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.