文档章节

在 Web 容器中使用 Spring + CXF 发布 WS(二) --SOAP 及其安全控制

辉_Dreaming
 辉_Dreaming
发布于 2017/05/31 18:07
字数 3796
阅读 48
收藏 0

一 WSDL ,SOAP基本概念

通过上篇已经基本掌握了使用CXF开发基于SOAP的WS.在此基础上了解一下WSDL,SOAP等一些常见的术语.

WSDL 的全称是 Web Services Description Language(Web 服务描述语言),用于描述 WS 的具体内容。

当成功发布一个 WS 后,就能在浏览器中通过一个地址查看基于 WSDL 文档,它是一个基于 XML 的文档。一个典型的 WSDL 地址如下:

http://localhost:8080/webservice/testService?wsdl

其中的?wsdl必须带上,才能返回一个基于xml的文档.

一个典型的wdsl文档如下:

<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://webservices.chuyu.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="MyServiceImplService" targetNamespace="http://webservices.chuyu.com/">
<wsdl:types>...</wsdl:types>
<wsdl:message name="saygodby">...</wsdl:message>
<wsdl:message name="saygodbyResponse">...</wsdl:message>
<wsdl:message name="sayHelloResponse">...</wsdl:message>
<wsdl:message name="sayHello">...</wsdl:message>
<wsdl:portType name="Myservice">...</wsdl:portType>
<wsdl:binding name="MyServiceImplServiceSoapBinding" type="tns:Myservice">...</wsdl:binding>
<wsdl:service name="MyServiceImplService">...</wsdl:service>
</wsdl:definitions>

其中,definitions 是 WSDL 的根节点,它包含两个重要的属性:

  1. name:WS 名称,默认为“WS 实现类 + Service”,例如:HelloServiceImplService
  2. targetNamespace:WS 目标命名空间,默认为“WS 实现类对应包名倒排后构成的地址”,例如:http://soap_spring_cxf.ws.demo/
    *可以在 javax.jws.WebService 注解中配置以上两个属性值,但这个配置一定要在 WS 实现类上进行,WS 接口类只需标注一个 WebService 注解即可。

在 definitions 这个根节点下,有五种类型的子节点,它们分别是:

  1. types:描述了 WS 中所涉及的数据类型
  2. portType:定义了 WS 接口名称(endpointInterface)及其操作名称,以及每个操作的输入与输出消息
  3. message:对相关消息进行了定义(供 types 与 portType 使用)
  4. binding:提供了对 WS 的数据绑定方式
  5. service:WS 名称及其端口名称(portName),以及对应的 WSDL 地址.

    其中包括了两个重要信息:

  6. <wsdl:service name="MyServiceImplService">
    <wsdl:port binding="tns:MyServiceImplServiceSoapBinding" name="MyServiceImplPort">
    <soap:address location="http://localhost:8080/webservice/testService"/>
    </wsdl:port>
    </wsdl:service>

     

     portName:WS 的端口名称,默认为“WS 实现类 + Port”,例如:HelloServiceImplPort

    endpointInterface:WS 的接口名称,默认为“WS 实现类所实现的接口+Service”,例如:MyServiceImplService.

如果说wsdl只是一个描述文档的话,那SOAP就是具体的调用内容了.

其实 SOAP 就是一个信封(Envelope),在这个信封里包括两个部分,一是头(Header),二是体(Body)。用于传输的数据都放在 Body 中了,一些特殊的属性需要放在 Header 中(下面会看到)。

一般情况下,将需要传输的数据放入 Body 中,而 Header 是没有任何内容的,看起来整个 SOAP 消息是这样的:

 

查阅WS的相关资料及可能的应用场景,在实际的应用中可能会有以下的需求:

  1. WS 不应该让任何人都可以调用的,这样太不安全了,至少需要做一个身份认证
  2. 为了避免第三方恶意程序监控 WS 调用过程,能否对 SOAP Body 中的数据进行加密
  3. SOAP Header 中是否也可存放某些东西用于数据传输

在 WS 领域有一个很强悍的解决方案,名为 WS-Security,它仅仅是一个规范,在 Java 业界里有一个很权威的实现,名为 WSS4J。

下面我将一步步让您学会,如何使用 Spring + CXF + WSS4J 实现一个安全可靠的 WS 调用框架。

将以上的需求抽象为以下步骤:

  1. 认证 WS 请求
  2. 加密 SOAP 消息

 二 WS的请求身份认证

1.基于用户令牌的身份认证

    添加Jar包依赖

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-ws-security</artifactId>
    <version>${cxf.version}</version>
</dependency>

    服务端的cxf-servlet.xml 配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core"
       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 http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd">
        <!--1.  cxf-servlet.xml中import导入的文件不用自己创建,这是在依赖包中的。 -->
    <import resource="classpath:META-INF/cxf/cxf.xml"/>
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
    <bean id="myService" class="com.chuyu.webservices.MyServiceImpl" />
     <!--  2. webservice发布配置中implementor可以直接写入实现类,如:
    <jaxws:endpoint id="testService" implementor="test.service.impl.MyServiceImpl" address="/testService"/>
    -->
    <bean id="serverPasswordCallback" class="com.chuyu.util.ServerPasswordCallback"/>
    <jaxws:endpoint id="testService" implementor="#myService" address="/testService">
        <!--3.address参数是重点,这是webservice发布后其wsdl的相对路径,其绝对路径为应用访问路径/cxf拦截路径/address?wsdl-->
        <jaxws:inInterceptors>
            <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
            <bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor"/>
            <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
                    <constructor-arg>
                        <map>
                            <!-- action:UsernameToken使用基于“用户名令牌”的方式进行身份认证-->
                            <entry key="action" value="UsernameToken"/>
                            <!--密码加密策略.PasswordText:明文密码 ;PasswordDigest 密文密码-->
                            <entry key="passwordType" value="PasswordDigest"/>
                            <!--服务端别名 ,可不指定-->
                            <entry key="user" value="cxfServer"/>
                            <!--提供一个用于密码验证的回调处理器-->
                            <entry key="passwordCallbackRef">
                                <ref bean="serverPasswordCallback"/>
                            </entry>
                        </map>
                    </constructor-arg>
            </bean>
        </jaxws:inInterceptors>
    </jaxws:endpoint>

    <cxf:bus>
        <cxf:features>
            <cxf:logging/>
        </cxf:features>
    </cxf:bus>
</beans>

首先定义了一个基于 WSS4J 的拦截器(WSS4JInInterceptor),然后通过 <jaxws:inInterceptors> 将其配置到 testService上,最后使用了 CXF 提供的 Bus 特性,只需要在 Bus 上配置一个 logging feature,就可以监控每次 WS 请求与响应的日志了。

注意:这个 WSS4JInInterceptor 是一个 InInterceptor,表示对输入的消息进行拦截,同样还有 OutInterceptor,表示对输出的消息进行拦截。由于以上是服务器端的配置,因此我们只需要配置 InInterceptor 即可,对于客户端而言,我们可以配置 OutInterceptor(下面会看到)。

回调函数的实现类

package com.chuyu.util;

import org.apache.ws.security.WSPasswordCallback;
import org.springframework.stereotype.Component;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component
public class ServerPasswordCallback implements CallbackHandler {
    /**
     * 假定userMap为存放的客户端和服务端的名称和密码,
     * 在实际应用场景中可以使用数据库等存储机制来验证用户名和密码组合.
     */
    private static final Map<String, String> userMap = new HashMap<String, String>();

    static {
        userMap.put("client", "clientpass");
        userMap.put("server", "serverpass");
    }
    @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        //将 JDK 提供的 javax.security.auth.callback.Callback 转型为 WSS4J 提供的
        //org.apache.wss4j.common.ext.WSPasswordCallback
        WSPasswordCallback callback=(WSPasswordCallback) callbacks[0];
       //客户端标识(用户名)
        String clientUsername=  callback.getIdentifier();
      //密码
        String clientPassword = userMap.get(clientUsername);

        if (serverPassword != null) {
            callback.setPassword(serverPassword);
        }
         else {
            throw   new SecurityException("验证失败");
        }
    }
}

客户端的cxf-servlet配置

<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">
        
        
        <import resource="classpath:META-INF/cxf/cxf.xml"/>
        <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
       <!-- 客户端xml配置 -->
       <bean id="webTest" class="com.chuyu.client.Myservice" 
                          factory-bean="client" factory-method="create"/>  
       
       <bean id="client" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean"> 
            <property name="address" 
                      value="http://192.168.9.47:8080/ws-demo-server/webservice/testService?wsdl">
            </property>  
            <property name="serviceClass" value="com.chuyu.client.Myservice"></property> 
             <property name="outInterceptors">  
                <list>  
                    <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />    
                    <bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />    
                    <bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">    
                        <constructor-arg>    
                            <map>    
                                <entry key="action" value="UsernameToken" />    
                                <entry key="passwordType"    
                                    value="PasswordDigest" />    
                                <entry key="user" value="client" />    
                                <entry key="passwordCallbackRef">    
                                    <ref bean="clientPasswordCallback" />    
                                </entry>    
                            </map>    
                        </constructor-arg>    
                    </bean>    
                </list>  
        </property>  
       </bean>
       <bean id="clientPasswordCallback" class="com.chuyu.webservice.ClientPasswordCallback"></bean> 
</beans>

与服务端的配置类似,注释可以在服务端中找.

客户端的密码设置回调函数.

package com.chuyu.webservice;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.wss4j.common.ext.WSPasswordCallback;
import org.springframework.stereotype.Component;

@Component
public class ClientPasswordCallback implements CallbackHandler{

    @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
            WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];
            System.out.println("identifier: " + callback.getIdentifier()); //客户端标识 及xml中配置的User用户名
            callback.setPassword("clientpass");        
            
    }
    
    

}

测试:

使用spring提供的test以及Junit 单元测试来测试

package com.chuyu.webservice;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.chuyu.client.Myservice;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:cxf-servlet.xml","classpath:spring.xml"})
public class Clientclass{
    @Autowired
    private Myservice webTest;
    
    @Test
    public void testSayhello(){
  
        System.out.println(webTest.sayHello("张三"));
    }

}

如果指定的表标识符不存在即xml配置的user,在服务端的回调函数中userMap中不存在该键则抛出异常:

当密码和用户名都正确时,可以在控制台看到发送的SOAP信息

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1">
      <wsse:UsernameToken wsu:Id="UsernameToken-c9246452-7027-4bc4-9775-b8e34fd4439c">
        <wsse:Username>cxfClient</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">clientpass</wsse:Password>
      </wsse:UsernameToken>
    </wsse:Security>
  </SOAP-ENV:Header>
  <soap:Body>
    <ns2:sayHello xmlns:ns2="http://webservices.chuyu.com/">
      <name>张三</name>
    </ns2:sayHello>
  </soap:Body>
</soap:Envelope>

可见,在 SOAP Header 中提供了 UsernameToken 的相关信息,但 Username 与 Password 都是明文的,SOAP Body 也是明文的,这显然不是最好的解决方案。

如果您将 passwordType 由 PasswordText 改为 PasswordDigest(服务端与客户端都需要做同样的修改),那么就会看到一个加密过的密码,在此不做演示.

对于上面的这种方式,根据SOAP的信息可以看出,加密的过程实际上是在SOAP的Header头部加上了验证信息,我们也可以采用另外的一种方式直接在header头部加上验证信息

              通过SoapHeader来增强Web Service的安全性

服务端的cxf-servlet.xml配置

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core"
       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 http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd">
        <!--1.  cxf-servlet.xml中import导入的文件不用自己创建,这是在依赖包中的。 -->
    <import resource="classpath:META-INF/cxf/cxf.xml"/>
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
    <bean id="myService" class="com.chuyu.webservices.MyServiceImpl" />
     <!--  2. webservice发布配置中implementor可以直接写入实现类,如:
    <jaxws:endpoint id="testService" implementor="test.service.impl.MyServiceImpl" address="/testService"/>
    -->
    <!--<bean id="serverPasswordCallback" class="com.chuyu.util.ServerPasswordCallback"/>-->
    <jaxws:endpoint id="testService" implementor="#myService" address="/testService">
        <!--3.address参数是重点,这是webservice发布后其wsdl的相对路径,其绝对路径为应用访问路径/cxf拦截路径/address?wsdl-->
        <jaxws:inInterceptors>
            <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
            <bean class="com.chuyu.util.ReadSoapHeader"></bean>
            <!--<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor"/>-->
            <!--<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">-->
                    <!--<constructor-arg>-->
                        <!--<map>-->
                            <!--&lt;!&ndash; action:UsernameToken使用基于“用户名令牌”的方式进行身份认证&ndash;&gt;-->
                            <!--<entry key="action" value="UsernameToken"/>-->
                            <!--&lt;!&ndash;密码加密策略.PasswordText:明文密码 ;PasswordDigest 密文密码&ndash;&gt;-->
                            <!--<entry key="passwordType" value="PasswordDigest"/>-->
                            <!--&lt;!&ndash;服务端别名 ,可不指定&ndash;&gt;-->
                            <!--<entry key="user" value="cxfServer"/>-->
                            <!--&lt;!&ndash;提供一个用于密码验证的回调处理器&ndash;&gt;-->
                            <!--<entry key="passwordCallbackRef">-->
                                <!--<ref bean="serverPasswordCallback"/>-->
                            <!--</entry>-->
                        <!--</map>-->
                    <!--</constructor-arg>-->
            <!--</bean>-->
        </jaxws:inInterceptors>
    </jaxws:endpoint>

    <cxf:bus>
        <cxf:features>
            <cxf:logging/>
        </cxf:features>
    </cxf:bus>
</beans>

ReadSoapHeader类

package com.chuyu.util;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.NodeList;

import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;

/**
 * Created by caowenhui on 2017/6/6.
 */
public class ReadSoapHeader extends AbstractPhaseInterceptor<SoapMessage> {
    //消息输入拦截
    private SAAJInInterceptor saa=new SAAJInInterceptor();
    public ReadSoapHeader(){
        //指定拦截阶段
        super(Phase.PRE_PROTOCOL);
        getAfter().add(SAAJInInterceptor.class.getName());
    }
    public void handleMessage(SoapMessage message) throws Fault {
//        获取Soap信息的xml表示
        SOAPMessage mess=message.getContent(SOAPMessage.class);
        if(mess==null){
            saa.handleMessage(message);
            mess=message.getContent(SOAPMessage.class);
        }
        //获取SOAP xml的Hander头部信息
        SOAPHeader head=null;
        try {
            head = mess.getSOAPHeader();
        } catch (SOAPException e) {
            e.printStackTrace();
        }
        if(head==null){
            return;
        }
        //用户名和密码节点
        NodeList nodes=head.getElementsByTagName("tns:spId");
        NodeList nodepass=head.getElementsByTagName("tns:spPassword");
        System.out.println(nodes.item(0).getTextContent());
        System.out.println(nodepass.item(0).getTextContent());
        if(nodes.item(0).getTextContent().indexOf("client")!=-1){
            if(nodepass.item(0).getTextContent().equals("clientpass")){
                System.out.println("认证成功");
            }
        }
        else{
            SOAPException soapExc=new SOAPException("认证错误");
            throw new Fault(soapExc);
        }
    }
}

客户端cxf-servlet配置与服务端大同小异

cxf-servlet.xml配置

<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">
        
        
        <import resource="classpath:META-INF/cxf/cxf.xml"/>
        <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
       <!-- 客户端xml配置 -->
       <bean id="webTest" class="com.chuyu.client.Myservice" factory-bean="client" factory-method="create"/>  
       
       <bean id="client" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean"> 
            <property name="address" value="http://192.168.9.47:8080/ws-demo-server/webservice/testService?wsdl"></property>  
            <property name="serviceClass" value="com.chuyu.client.Myservice"></property> 
             <property name="outInterceptors">  
                <list>  
                    <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />   
                    <bean class="com.chuyu.webservice.AddSoapHeader"></bean>   
                   <!--  <bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />  -->   
                    <!-- <bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">    
                        <constructor-arg>    
                            <map>    
                                <entry key="action" value="UsernameToken" />    
                                <entry key="passwordType"    
                                    value="PasswordDigest" />    
                                <entry key="user" value="client" />    
                                <entry key="passwordCallbackRef">    
                                    <ref bean="clientPasswordCallback" />    
                                </entry>    
                            </map>    
                        </constructor-arg>    
                    </bean>  -->   
                </list>  
        </property>  
       </bean>
      <!--  <bean id="clientPasswordCallback" class="com.chuyu.webservice.ClientPasswordCallback"></bean>  -->
</beans>

AddSoapHeader.java

package com.chuyu.webservice;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import javax.xml.namespace.QName;

import org.apache.cxf.binding.soap.SoapHeader;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;



public class AddSoapHeader extends AbstractSoapInterceptor{
    private static String nameURI="http://www.WsAuthentication.com//authentication";  
    public AddSoapHeader(){  
        // 指定该拦截器在哪个阶段被激发  
        super(Phase.WRITE);  
    }  
    @Override
    public void handleMessage(SoapMessage message) throws Fault {
        SimpleDateFormat sd=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        Date date=new Date();  
        String time =sd.format(date);
        String spPassword="client"; 
        String spName="clientpass";  
        
        QName qname=new QName("RequestSOAPHeader");  
        Document doc=DOMUtils.createDocument();  
        
        Element spId=doc.createElement("tns:spId");  
        spId.setTextContent(spName);  
          
        Element spPass=doc.createElement("tns:spPassword");  
        spPass.setTextContent(spPassword);  
        Element root=doc.createElementNS(nameURI, "tns:RequestSOAPHeader");  
        root.appendChild(spId);  
        root.appendChild(spPass);  
        
        SoapHeader head=new SoapHeader(qname,root);  
        List<Header> headers=message.getHeaders();  
        headers.add(head);  
          
    }

}

测试同样采用上面的单元测试.发送的SAOP信息

Header节点下即为添加的SAOP验证信息

2.基于数字签名的身份认证

数字签名从字面上理解就是一种基于数字的签名方式。也就是说,当客户端发送 SOAP 消息时,需要对其进行“签名”,来证实自己的身份,当服务端接收 SOAP 消息时,需要对其签名进行验证(简称“验签”)。

在客户端与服务端上都有各自的“密钥库”,这个密钥库里存放了“密钥对”,而密钥对实际上是由“公钥”与“私钥”组成的。当客户端发送 SOAP 消息时,需要使用自己的私钥进行签名,当客户端接收 SOAP 消息时,需要使用客户端提供的公钥进行验签。

参考Apache CXF官网帮助文档上的介绍:(http://cxf.apache.org/docs/ws-security.html)

因为有请求就有相应,所以客户端与服务端的消息调用实际上是双向的,也就是说,客户端与服务端的密钥

库里所存放的信息是这样的:

  • 客户端密钥库:客户端的私钥(用于签名)、服务端的公钥(用于验签)
  • 服务端密钥库:服务端的私钥(用于签名)、客户端的公钥(用于验签)

总结成一句话:使用自己的私钥进行签名,使用对方的公钥进行验签。

 

新建keystore.bat,使用 JDK 提供的 keytool 命令行工具创建数字证书

@echo off
 
keytool -genkeypair -alias server -keyalg RSA -dname "cn=server" -keypass serverpass -keystore server_store.jks -storepass storepass
keytool -exportcert -alias server -file server_key.rsa -keystore server_store.jks -storepass storepass
keytool -importcert -alias server -file server_key.rsa -keystore client_store.jks -storepass storepass -noprompt
del server_key.rsa
 
keytool -genkeypair -alias client -dname "cn=client" -keyalg RSA -keypass clientpass -keystore client_store.jks -storepass storepass
keytool -exportcert -alias client -file client_key.rsa -keystore client_store.jks -storepass storepass
keytool -importcert -alias client -file client_key.rsa -keystore server_store.jks -storepass storepass -noprompt
del client_key.rsa

运行该批处理程序,将生成两个文件:server_store.jks 与 client_store.jks,随后将 server_store.jks 放入服务端的 classpath 下,将 client_store.jks 放入客户端的 classpath 下.

服务端cxf配置
 

<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">
        
        
        <import resource="classpath:META-INF/cxf/cxf.xml"/>
        <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
       <!-- 客户端xml配置 -->
       <bean id="webTest" class="com.chuyu.client.Myservice" factory-bean="client" factory-method="create"/>  
       
       <bean id="client" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean"> 
            <property name="address" value="http://192.168.9.47:8080/ws-demo-server/webservice/testService?wsdl"></property>  
            <property name="serviceClass" value="com.chuyu.client.Myservice"></property> 
             <property name="outInterceptors">  
                <list>  
                    <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />   
                   <!--  <bean class="com.chuyu.webservice.AddSoapHeader"></bean>    -->
                    <bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor"/>  
                    <bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">    
                        <constructor-arg>    
                            <map> 
                                <!-- 签名(使用自己的私钥) -->   
                                <entry key="action" value="Signature" />    
                                <entry key="signaturePropFile" value="client_sign.properties"/>
                                <entry key="signatureUser" value="client"/> 
                                <entry key="passwordCallbackRef">    
                                    <ref bean="clientPasswordCallback" />    
                                </entry>    
                            </map>    
                        </constructor-arg>    
                    </bean>   
                </list>  
        </property>  
       </bean>
      <!--  <bean id="clientPasswordCallback" class="com.chuyu.webservice.ClientPasswordCallback"></bean>  -->
</beans>

其中 action 为 Signature,client.properties 内容如下

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.file=server_store.jks
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=storepass

客户端配置:

 //.......相同省略
<bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
    <constructor-arg>
        <map>
            <!-- 签名(使用自己的私钥) -->
            <entry key="action" value="Signature"/>
            <entry key="signaturePropFile" value="client.properties"/>
            <entry key="signatureUser" value="client"/>
            <entry key="passwordCallbackRef" value-ref="clientPasswordCallback"/>
        </map>
    </constructor-arg>
</bean>
//.....省略

其中 action 为 Signature,client.properties 内容如下

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.file=client_store.jks
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=storepass

通过单元测试结果如下:

3.SOAP消息的签名与加/解密

WSS4J 除了提供签名与验签(Signature)这个特性以外,还提供了加密与解密(Encrypt)功能.

服务端:

客户端:

其中的回调函数与第一章节中相同.可以看到,发送的消息也被加密了.

参考文献:https://my.oschina.net/huangyong/blog/287791

 

© 著作权归作者所有

共有 人打赏支持
辉_Dreaming

辉_Dreaming

粉丝 5
博文 45
码字总数 45151
作品 0
武汉
Web Service 那点事儿(2)—— 使用 CXF 开发 SOAP 服务

选框架犹如选媳妇,选来选去,最后我还是选了“丑媳妇(CXF)”,为什么是它?因为 CXF 是 Apache 旗下的一款非常优秀的 WS 开源框架,具备轻量级的特性,而且能无缝整合到 Spring 中。 其实...

黄勇
2014/07/02
0
45
使用cxf发布webservice总结

一、概念 1、什么是webservice Web service是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML标准来描述、发布、发现、协调和配置这些应用程序,用于开发分...

漂泊者及其影子
2014/08/21
0
0
Web Service 那点事儿(4)—— 使用 CXF 开发 REST 服务

现在您已经学会了如何使用 CXF 开发基于 SOAP 的 Web 服务,也领略了 Spring + CXF 这个强大的组合,如果您错过了这精彩的一幕,请回头看看这篇吧: [Web Service 那点事儿(2) —— 使用 ...

黄勇
2014/07/23
0
43
使用 Apache CXF 实现 Web Service 详解

1.Web service的概念 什么是WebService呢?从表面上看,Web Service就是一个应用程序,它向外界暴露出一个能够通过Web进行调用的API。这就是说,你能够用编程的方法通过Web调用来实现某个功...

核桃白果
2013/08/08
0
10
Spring和Apache CXF,ActiveMQ整合记录

Spring 3.2.8 Apache CXF 1.6 ActiveMQ 5.9.0 Tomcat 8.0.3 MyEclipse 2013 1. Spring需要的jar包: spring-aop(aop支持) spring-beans(配置spring bean必须的包) spring-context(应用上......

精神病的羽毛球
2014/04/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Java Lock接口分析之ReentantReadWriteLock

ReentantReadWriteLock读写锁,在读线程多余写线程的并发环境中能体现出优异的性能,相比于synchronized与ReentrantLock这种独占式锁的模型,ReentantReadWriteLock采用独占式写锁与共享式读...

我爱春天的毛毛雨
27分钟前
1
0
EFK (Fluentd ElasticSearch Kibana) 采集nginx日志

本文描述如何通过FEK组合集中化nginx的访问日志。本人更喜欢按顺序来命名,所以使用FEK而不是EFK. 首先在nginx服务器上执行以下操作. 安装ruby http://blog.csdn.net/chenhaifeng2016/artic...

xiaomin0322
29分钟前
1
0
一键下载:将知乎专栏导出成电子书

老是有同学问,学了 Python 基础后不知道可以做点什么来提高。今天就再用个小例子,给大家讲讲,通过 Python 和爬虫,可以完成怎样的小工具。 在知乎上,你一定关注了一些不错的专栏(比如 ...

crossin
38分钟前
2
0
synchronized 之 对象锁 和 类锁

一、synchronized(object) 如果object没有被加锁,则获取object的锁;如果object已经被加锁则等待object的锁被释放。 二、需要加锁的情景 多线程共享同一资源会引起线程安全的情况下,才需要...

MyOldTime
39分钟前
7
0
tomcat 单机/多机 部署多应用

一.单机部署多应用: 1.在 linux 下解压安装两个 tomcat:tomcat1, tomcat2; 2.修改 /etc/profile, 增加 tomcat 环境变量: path 中加上 重新加载配置文件 source /etc/profile 3.修改 tomc...

imbiao
50分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部