CXF系列之JAX-WS:基于SOAP的安全控制

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

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

本文是基于CXF与Spring集成,基础之上的,CXF与Spring集成请看:CXF系列之JAX-WS:与Spring3集成并在tomcat部署

其实您需要做也就是两件事情:

  1. 认证WS请求
  2. 加密SOAP消息
怎样对 WS 进行身份认证呢?可使用如下解决方案:

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

第一步、添加CXF提供的WS-Security的依赖

也就是相关jar包,CXF安装包里都有自带的,比如CXF3.0安装包里:
cxf-rt-ws-security-3.0.10.jar,将其添加到构建路径中,其底层实现还是WSS4J,CXF仅仅是对其做了一个封装而已。

第二步、完成服务端CXF相关配置

spring.xml配置:
  
  
  
  
  
      
  
 
spring-cxf.xml配置:


       
     

    
        
            
                
                
                
                
            
        
    

    
        
            
        
    

    
        
            
        
    


首先定义了一个基于WSS4J的拦截器(WSS4JInInterceptor),然后通过将其配置到helloService上,最后使用了CXF提供的Bus特性,只需要在Bus上配置一个logging feature,就可以监控每次WS请求与响应的日志了。
注意这个WSS4JInInterceptor是一个InInterceptor,表示对输入的消息进行拦截,同样也有OutInterceptor,表示对输出消息进行拦截。由于以上是对服务端的配置,因此我们只需要配置InInterceptor即可,对于OutInterceptor,我们可以在客户端进行配置。
有必要对以上配置WSS4JInInterceptor构造函数的参数进行说明:
  1. action=UsernameToken:表示基于“用户名令牌”的方式,进行身份认证。
  2. passwordType=passwordText:表示密码以明文的方式出现。
  3. passwordCallbackRef=serverPasswordCallback,需要提供一个用于密码验证的回调处理器(CallbackHandler)。
一下便是ServerPasswordCallback的具体实现:
package com.test.handler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

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;

public class ServerPasswordCallback implements CallbackHandler {

	 private static final Map userMap = new HashMap();
	
	 static{
		 userMap.put("client", "clientpass");
	     userMap.put("server", "serverpass");
	 }
	
	@Override
	public void handle(Callback[] callbacks) throws IOException,
			UnsupportedCallbackException {
		 WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];
		 
		 String clientUsername = callback.getIdentifier();
		 String serverPassword = userMap.get(clientUsername);
		 
		 if (serverPassword != null) {
	            callback.setPassword(serverPassword);
	        }
	}

}
可见,它实现了javax.security.auth.callback.CallbackHandler接口,这是JDK提供的用于安全认证的回调处理器接口,在代码中,提供了两个用户:server和client,用户名和密码都放在userMap中,这里需要将JDK提供的javax.security.auth.callback.Callback转型为WSS4J提供的org.apache.wss4j.common.ext.WSPasswordCallback,在handle方法中实现对客户端密码的验证,最终需要将密码放入到callback对象中。

第三步、完成客户端CXF相关配置




	

    
        
            
                
                
                
                
                
            
        
    

    
        
            
        
    

注意:这是使用的是WSS4JOutInterceptor拦截器,它是一个OutInterceptor,使客户端对输出的消息进行拦截。
WSS4JOutInterceptor的配置WSS4JInInterceptor的配置大同小异,这里需要提供客户端的用户名(user=client),还需要提供一个客户端密码回调处理器(passwordCallbackRef = clientPasswordCallback),代码如下:
package com.test.handler;

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;

public class ClientPasswordCallback implements CallbackHandler {

	@Override
	public void handle(Callback[] callbacks) throws IOException,
			UnsupportedCallbackException {
		WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];
        callback.setPassword("clientpass");

	}

}
在ClientPasswordCallback中,无非是这只客户端用户的密码,其他的什么也不用做了,客户端密码只能通过回调处理器的方式进行提供,而不能再spring配置文件中进行配置。

第四步:调用WS并观察控制台日志

部署应用并启动tomcat,再次调用WS,此时在控制台里的Inbound Message中会看到如下Payload:
clientclientpasscxf

可见,在SOAP Header中提供了UsernameToken的相关信息,但Username与Password都是明文的,SOAP Body也是明文的,这显然不是最好的解决方案,
如果你将passwordType由PasswordText改为PasswordDigest(服务端与客户端都需要做同样的修改),那么就会看到一个加密过的密码:
clientHvElE//KB8gGhIv5u8RKI+hoUYc=2+E62ViJx7kbw13ZlMgGBA==2016-11-28T02:57:07.932Zcxf
除了这种基于用户名与密码的身份认证方式外,还有一种更加安全的身份认证方式,名为“数字签名”。

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

数字签名从字面上理解就是一种基于数字的签名方式。也就是说,当客户端发送 SOAP 消息时,需要对其进行“签名”,来证实自己的身份,当服务端接收 SOAP 消息时,需要对其签名进行验证(简称“验签”)。
在客户端与服务端上都有各自的“密钥库”,这个密钥库里存放了“密钥对”,而密钥对实际上是由“公钥”与“私钥”组成的。当客户端发送 SOAP 消息时,需要使用自己的私钥进行签名,当客户端接收 SOAP 消息时,需要使用客户端提供的公钥进行验签。
因为有请求就有相应,所以客户端与服务端的消息调用实际上是双向的,也就是说,客户端与服务端的密钥库里所存放的信息是这样的:
  • 客户端密钥库:客户端的私钥(用于签名)、服务端的公钥(用于验签)
  • 服务端密钥库:服务端的私钥(用于签名)、客户端的公钥(用于验签)
记住一句话:使用自己的私钥进行签名,使用对方的公钥进行验签。
因此,生成密钥库是我们第一步需要做的事情。

第一步:生成密钥库

现在你需要创建一个keystore.bat的批处理文件,其内容如下:
@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
在以上这些命令中,使用了 JDK 提供的 keytool 命令行工具,关于该命令的使用方法,可点击以下链接:
http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html
运行该批处理程序,将生成两个文件:server_store.jks 与 client_store.jks,随后将 server_store.jks 放入服务端的 classpath 下,将 client_store.jks 放入客户端的 classpath 下。如果您在本机运行,那么本机既是客户端又是服务端。

第二步:完成服务端CXF相关配置

spring-cxf.xml文件配置:


       
     
    
    
        
            
                
                
                
            
        
    

    
        
            
        
    

    
        
            
        
    


其中action为Signature,server.properties内容如下:
org.apache.ws.security.crypto.provider=org.apache.wss4j.common.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

第三步:完成客户端CXF相关配置

spring-client.xml文件配置:



	
    
    
        
            
                
                
                
				
                
            
        
    

    
        
            
        
    


其中action为Signature,client.properties文件内容如下:
org.apache.ws.security.crypto.provider=org.apache.wss4j.common.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
此外,客户端同样需要提供签名用户(signatureUser)和密码回调处理器(passwordCallbackRef)。

第四步:发布、调用WS服务,并观察控制台日志

+CL/+5vsXW07y9PThudROCUYV30=PfqRLe2lIKHOLC/LGGsVdMLFTPx9/88fGDNLgwAWS+/XTsVvPRKF5tAEGo5KQ1bO2DBSUeJzCV776EKXC6S1GGDP70DU43eyOPfnZfYPF8NJZQFCJJe9Ir1lUZ8XJNEK9Puriw2XM/UO+P4/OJVRJql/EFFuhES1cNlsvDxM5yYkfEwKhv8qc5IuwvviJMN7jIbrQOFP8/xDE35wabrLp+xOUNLGRiyWDPAVEb+rYdPbHwJHVo/KwWKkje43IRIFc+KXL833aQHm27UBWuOcHl5tL4aZHydcc8/UvWU7IK9soU+zis3R1z9Ks9jwDlsHkvR6xT+cCGxfsKSqR4m+ZA==CN=client508496482cxf
可见,数字签名是一种更为安全的身份认证方式,但无法对SOAP Body中的数据进行加密,究竟怎样才能加密并解密SOAP中的数据呢?

3、SOAP消息的加密与解密

WSS4J 除了提供签名与验签(Signature)这个特性以外,还提供了加密与解密(Encrypt)功能,您只需要在服务端与客户端的配置中稍作修改即可。
服务端:


       
     
    
    
    
        
            
                
                
                
                
                
                
                
            
        
    

    
        
            
        
    

    
        
            
        
    

客户端:



	
    
    
        
            
                
                
                
                
				
                
                
                
                
            
        
    

       
        
            
        
    

可见,客户端发送 SOAP 消息时进行签名(使用自己的私钥)与加密(使用对方的公钥),服务端接收 SOAP 消息时进行验签(使用对方的公钥)与解密(使用自己的私钥)。
在您看到的 SOAP 消息应该是这样的:
CN=server979617998fk/AirRnUfefQwjWViwuCNbKM7KbVVKLwF5xn+V1g4XfHZZ8/i/QrXcfnmGcojub8nvLZNg6ux2/PT+Nay6Sb9+PRuC8vqsGSbWSVRv9P1nNMpN4Ie5CBCwLM9iYCfgMSCx9KfqnRyxFlQBRcHmVKhwicUuszFSR/IrZ1BGAjtJw11EO5cCSmKJcrI2Pl8qB63u2LUE8lN0TjbkoIo9ll/PlamL5sw06rTa1ZDa/07rUZzIfhoU4grRHCY1skkdy3RQwnjG3mgpve7eZ1DTf05y3TW+NDQgDpv871V/nS4x0thYIIX6umgIOFMoV/MGzQ1ccyADNh8P27lI7yjbI4g==INGDwn+fnUGbhuwdNCvujDahddc=NASMeogydnAQE0oAEDf0V1/llihU2kuD/R0ZforidXDX+EJOZkfQ6uc1XIhZGI31yxYjZ5h3WxSZaIJuurxPZiq+DNlDEKUntYKMOKljClJyjjZTCGUor7fuiIgh9W2tbarQewxfWRes0I5kNWREebpyN3qkYMb0MRBwl5r2J8guJYCgr2zVvK6Bo08aSuIpSR3/ylrAgV3UaVIsM00fV9OEXFYpbSJGbrE+VV2uoDoCjN805MT0kyaYjOTPQSZdYNy/pnGUV8uxVVD55lKIdiyabnGC+J+wG2u80JeILb5m6wvtLyHSk2bgQeAqdZ3DTqTz7Bn6Up4xX8C0L+ifrw==CN=client508496482BWCrzKudYIxdpOOCE+y3cSnSoJiZu22p4FxgSR2grJ8IVD2hHmMTBQlPT+o1RruK3kg6jM/fGlgabHzpwHBQ+z4uTTsAuXAnkkAeB9UPaxC2iZ0b9y4kBlCD/qQEBb6M+vRD05SvIgv5QhJPpf72Vg==

可见,SOAP 请求不仅签名了,而且还加密了,这样的通讯更加安全可靠。

4. 总结

本文的内容有些多,确实需要稍微总结一下:

  1. WSDL 是用于描述 WS 的具体内容的
  2. SOAP 是用于封装 WS 请求与响应的
  3. 可使用“用户令牌”方式对 WS 进行身份认证(支持明文密码与密文密码)
  4. 可使用“数字签名”方式对 WS 进行身份认证
  5. 可对 SOAP 消息进行加密与解密











你可能感兴趣的:(CXF系列之JAX-WS:基于SOAP的安全控制)