善忘技术夹 Logo
善忘技术夹
技术文章 · 6479 阅读

CXF基于spring配置X.509数字证书安全验证

首先开发服务器端

用的是spring集成cxf,可以直接发布到tomcat中。 新建一个web项目:cxftest,导入cxf和spring相关jar包,pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.skong</groupId>
<artifactId>cxf-test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>skong-cxf-demo</name>
<description>通过类来生成wsdl文件, 通过pom中的cxf-codegen生成对应的客户端,服务端</description>
<properties>
    <cxf.version>3.1.8</cxf.version>
    <spring.version>4.2.3.RELEASE</spring.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxws</artifactId>
        <version>${cxf.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>${cxf.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-ws-security</artifactId>
        <version>${cxf.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-tools-java2ws</artifactId>
        <version>${cxf.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http-jetty</artifactId>
        <version>${cxf.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
       
    </plugins>
</build>
</project>

在web.xml中配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
     xmlns="http://java.sun.com/xml/ns/javaee"
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
     id="WebApp_ID" version="3.0">
<display-name>cxfserver</display-name>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:/applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Spring 刷新Introspector防止内存泄露 -->
<listener>
    <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<!-- CXF Servlet -->
<servlet>
    <servlet-name>cxfservlet</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>cxfservlet</servlet-name>
    <!-- 匹配/services下的所有请求 -->
    <url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<!-- End CXF Servlet -->

<welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

然后开发WebService接口: HelloService:

import org.apache.cxf.interceptor.InInterceptors;

import org.apache.cxf.interceptor.OutInterceptors;

import javax.jws.WebService;

/**

- Created by sily on 2016/10/25.
  */
  @WebService
  @InInterceptors(interceptors = {"org.apache.cxf.interceptor.LoggingInInterceptor"})
  @OutInterceptors(interceptors = {"org.apache.cxf.interceptor.LoggingOutInterceptor"})
  public interface HelloService {
  String say(String name);
  }

开发实现类: HelloServiceImpl:

package com.skong.springcxf;

import org.springframework.stereotype.Component;

import javax.jws.WebService;

/**
 * Created by sily on 2016/10/25.
 */
@WebService
@Component
public class HelloServiceImpl implements HelloService {
    public String say(String name) {
        return "hello " + name;
    }
}



import javax.jws.WebService;

/**

  • Created by sily on 2016/10/25. */ @WebService @Component public class HelloServiceImpl implements HelloService { public String say(String name) { return “hello ” + name; } }

下面在spring中集成,直接发布到tomcat:beans.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:jaxws="http://cxf.apache.org/jaxws"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<!-- jar包中自带的cxf文件夹下的*.xml文件 -->
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<!--<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />-->
<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
<!-- 配置服务端方法所在的类及数字签名 -->
<jaxws:endpoint id="ISecuriyService" implementor="com.tongtech.ti.cxf.demo.security.service.ISecuriyServicePortImpl"
                address="/sec">
    <!-- 通过拦截器对客户端发送的数据和签名进行解密处理 -->
    <jaxws:inInterceptors>
        <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
            <constructor-arg>
                <map>
                    <entry key="action" value="Timestamp Signature Encrypt"/>
                    <entry key="signaturePropFile" value="cert/Server_SignVerf.properties"/>
                    <entry key="decryptionPropFile" value="cert/Server_Decrypt.properties"/>
                    <entry key="passwordCallbackClass"
                           value="com.tongtech.ti.cxf.demo.security.X509.server.UTPasswordServerCallBack"/>
                </map>
            </constructor-arg>
        </bean>
    </jaxws:inInterceptors>
    <!-- 通过拦截器将服务端发送给客户端的数据和签名进行加密 -->
    <jaxws:outInterceptors>
        <bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
            <constructor-arg>
                <map>
                    <entry key="action" value="Timestamp Signature Encrypt"/>
                    <entry key="user" value="serverprivatekey"/>
                    <entry key="encryptionUser" value="clientpublickey"/>
                    <entry key="signaturePropFile" value="cert/Server_Decrypt.properties"/>
                    <entry key="encryptionPropFile" value="cert/Server_SignVerf.properties"/>
                    <entry key="passwordCallbackClass"
                           value="com.tongtech.ti.cxf.demo.security.X509.server.UTPasswordServerCallBack"/>
                </map>
            </constructor-arg>
            <property name="allowMTOM" value="true"></property>
        </bean>
    </jaxws:outInterceptors>
</jaxws:endpoint>
<jaxws:client id="renderClient" address="http://localhost:8080/ws/sec"
              serviceClass="com.tongtech.ti.cxf.demo.security.service.ISecuriyDemo">
    <jaxws:inInterceptors>
        <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
            <constructor-arg>
                <map>
                    <entry key="action" value="Timestamp Signature Encrypt"/>
                    <entry key="signaturePropFile" value="cert/Client_Encrypt.properties"/>
                    <entry key="decryptionPropFile" value="cert/Client_Sign.properties"/>
                    <entry key="passwordCallbackClass"
                           value="com.tongtech.ti.cxf.demo.security.X509.server.UTPasswordServerCallBack"/>
                </map>
            </constructor-arg>
        </bean>
    </jaxws:inInterceptors>
    <jaxws:outInterceptors>
        <bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
            <constructor-arg>
                <map>
                    <entry key="action" value="Timestamp Signature Encrypt"/>
                    <entry key="user" value="clientprivatekey"/>
                    <entry key="encryptionUser" value="serverpublickey"/>
                    <entry key="signaturePropFile" value="cert/Client_Sign.properties"/>
                    <entry key="encryptionPropFile" value="cert/Client_Encrypt.properties"/>
                    <entry key="passwordCallbackClass"
                           value="com.tongtech.ti.cxf.demo.security.X509.server.UTPasswordServerCallBack"/>
                </map>
            </constructor-arg>
            <property name="allowMTOM" value="true"></property>
        </bean>
    </jaxws:outInterceptors>
</jaxws:client>
</beans>

UTPasswordServerCallBack 证书密码回调类

package com.tongtech.ti.cxf.demo.security.X509.server;

import org.apache.wss4j.common.ext.WSPasswordCallback;

import javax.security.auth.callback.Callback;

import javax.security.auth.callback.CallbackHandler;

import javax.security.auth.callback.UnsupportedCallbackException;

import java.io.IOException;

/**

- Created by sily on 2016/10/24.
   */
  public class UTPasswordServerCallBack implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException,



            UnsupportedCallbackException {



        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];



        pc.setPassword("keypass");



        System.out.println("Server Identifier=" + pc.getIdentifier());



        System.out.println("Server Password=" + pc.getPassword());

    }

}

这个文件就是上面web.xml中引入的文件。

所有的配置都在spring的配置文件中,服务器端开发完毕,下面请看客户端的开发: 客户端的开发

客户端要想进行认证,首先也要同样实现一个回调类(大家可以测一下,用常规的不带认证的方法写一个Client.java,是无法访问webservice的)

这个回调函数的类如下: UTPasswordClientCallBack

package com.tongtech.ti.cxf.demo.security.X509.client;

import org.apache.wss4j.common.ext.WSPasswordCallback;

import javax.security.auth.callback.Callback;

import javax.security.auth.callback.CallbackHandler;

import javax.security.auth.callback.UnsupportedCallbackException;

import java.io.IOException;

/**

- Created by sily on 2016/10/24.
   */
  public class UTPasswordClientCallBack implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
        pc.setPassword("keypass");
        System.out.println("Client Identifier=" + pc.getIdentifier());
        System.out.println("Client Password=" + pc.getPassword());
    
    }

}

最后是客户端的完整测试代码: ClientCxfSpring

package com.tongtech.ti.cxf.demo.security.X509.client;

import com.skong.springcxf.HelloService;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* Created by sily on 2016/10/26.
   */
  public class ClientCxfSpring {
   public static void main(String[] args) {
       System.out.println("Start client.....");
       ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
               new String[] { "classpath:bean.xml" });
      
       HelloService renderClient = (HelloService) context.getBean("renderClient");
      
       System.out.println(renderClient.say("test"));
       System.exit(0);
   }
  }

生成证书的文件夹放入cert文件夹中

(一)生成证书 生成证书还是比较麻烦的,要用到jdk的一个工具——keytool 首先,创建客户端KeyStore和公钥 在命令行运行: 1、创建私钥和KeyStore:

Java代码

  1. keytool -genkey -alias clientprivatekey -keypass keypass -keystore Client_KeyStore.jks -storepass storepass -dname "CN=tongtech.com,C=CN" -keyalg RSA
    创建KeyStore,文件名字为Client_KeyStore.jks,里面有个名为clientprivatekey的私钥。 2、给私钥进行自签名:

Java代码

  1. keytool -selfcert -keystore Client_KeyStore.jks -storepass storepass -alias clientprivatekey -keypass keypass
    签名成功,无任何提示。 3、导出私钥 作用是导出的证书将作为公钥保存到TrustStore中。

Java代码

  1. keytool -export -alias clientprivatekey -file Client_PublicCert.cer -keystore Client_KeyStore.jks -storepass storepass
    如果成功,可以看到提示: 保存在文件中的认证 <Client_PublicCert.cer> 然后创建服务端KeyStore 1、创建私钥和KeyStore

Java代码

  1. keytool -genkey -alias serverprivatekey -keypass keypass -keystore Server_KeyStore.jks -storepass storepass -dname "CN=tongtech.com,C=CN" -keyalg RSA
    2、给私钥进行自签名

Java代码

  1. keytool -selfcert -keystore Server_KeyStore.jks -storepass storepass -alias serverprivatekey -keypass keypass
    3、导出私钥

Java代码

  1. keytool -export -alias serverprivatekey -file Server_PublicCert.cer -keystore Server_KeyStore.jks -storepass storepass
    接下来,将客户端公钥导入到服务端TrustStore中,将服务端公钥导入到客户端TrustStore中。 在命令行中输入:

Java代码

  1. keytool -import -alias clientpublickey -file Client_PublicCert.cer -keystore Server_TrustStore.jks -storepass storepass
    回车后会提示

引用 所有者:CN=tongtech.com, C=CN 签发人:CN=tongtech.com, C=CN 序列号:4cc7e86c 有效期: Wed Oct 27 16:53:00 CST 2010 至Tue Jan 25 16:53:00 CST 2011 证书指纹: MD5:FB:AB:71:9F:56:F3:CB:65:16:DC:52:E0:2D:27:FF:F6 SHA1:06:A8:B1:B4:E2:42:9D:B2:F7:99:E7:70:34:08:96:52:E1:CD:4A:76 签名算法名称:SHA1withRSA 版本: 3 信任这个认证? [否]: 打y即可,然后提示

引用 认证已添加至keystore中 同理,将服务端公钥导入到客户端TrustStore中

Java代码

  1. 同样会出现提示,打y回车,提示成功就可以了。 到这里会有个疑问,为什么都叫keystore?在最上面已经提到,KeyStore和TrustStore是概念上的区分。

(二)编写代码 将上面生成好的maven工程导入到eclipse中,在src/main/java下建立新的包,起名叫cert,将刚刚生成好的KeyStore和TrustStore拷到cert包下。 1、创建配置文件 建立客户端加密/解密配置:Client_Encrypt.properties

Java代码

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin  
org.apache.ws.security.crypto.merlin.keystore.type=jks  
org.apache.ws.security.crypto.merlin.keystore.password=storepass  
org.apache.ws.security.crypto.merlin.keystore.alias=serverpublickey  
org.apache.ws.security.crypto.merlin.file=cert/Client_TrustStore.jks 

建立客户端验签/签名配置:Client_Sign.properties

Java代码

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin  
org.apache.ws.security.crypto.merlin.keystore.type=jks  
org.apache.ws.security.crypto.merlin.keystore.password=storepass  
org.apache.ws.security.crypto.merlin.keystore.alias=clientprivatekey  
org.apache.ws.security.crypto.merlin.file=cert/Client_KeyStore.jks  

建立服务端加密/解密配置:Server_Decrypt.properties

Java代码

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin  
org.apache.ws.security.crypto.merlin.keystore.type=jks  
org.apache.ws.security.crypto.merlin.keystore.password=storepass  
org.apache.ws.security.crypto.merlin.keystore.alias=serverprivatekey  
org.apache.ws.security.crypto.merlin.file=cert/Server_KeyStore.jks  

建立服务端验签/签名配置:Server_SignVerf.properties

Java代码

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin  
org.apache.ws.security.crypto.merlin.keystore.type=jks  
org.apache.ws.security.crypto.merlin.keystore.password=storepass  
org.apache.ws.security.crypto.merlin.keystore.alias=clientpublickey  
org.apache.ws.security.crypto.merlin.file=cert/Server_TrustStore.jks