使用 Spring + CXF + WSS4J 实现一个安全可靠的 WS 调用框架
WS-Security,它仅仅是一个规范, WSS4J是它的一个实现
what do 你 该 do?
1. 认证 WS 请求
2. 加密 SOAP 消息
第一种:基于用户令牌的身份认证
1.添加 CXF 提供的 WS-Security 的 Maven 依赖
( 底层实现仍为WSS4J,CXF 只是对其做了一个封装)
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-ws-security</artifactId>
<version>${cxf.version}</version>
</dependency>
2.服务端 CXF 相关配置
<?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:cxf="http://cxf.apache.org/core"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://cxf.apache.org/core
http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<!-- 用户认证(明文密码) -->
<!-- 表示使用基于“用户名令牌”的方式进行身份认证 -->
<entry key="action" value="UsernameToken"/>
<!-- 表示密码以明文方式出现 -->
<entry key="passwordType" value="PasswordText"/>
<!-- 需要提供一个用于密码验证的回调处理器(CallbackHandler) -->
<entry key="passwordCallbackRef" value-ref="serverPasswordCallback"/>
</map>
</constructor-arg>
</bean>
<jaxws:endpoint id="helloService" implementor="#helloServiceImpl" address="/soap/hello">
<jaxws:inInterceptors>
<ref bean="wss4jInInterceptor"/>
</jaxws:inInterceptors>
</jaxws:endpoint>
<cxf:bus>
<cxf:features>
<cxf:logging/>
</cxf:features>
</cxf:bus>
</beans>
首先定义了一个基于 WSS4J 的拦截器(WSS4JInInterceptor),然后通过 < jaxws:inInterceptors > 将其配置到 helloService 上,最后使用了 CXF 提供的 Bus 特性,只需要在 Bus 上配置一个 logging feature,就可以监控每次 WS 请求与响应的日志了。
3.客户端 CXF 相关配置
<context:component-scan base-package="com.cxf.c"/>
<bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<!-- 用户认证(明文密码) -->
<entry key="action" value="UsernameToken"/>
<entry key="user" value="client"/>
<entry key="passwordType" value="PasswordText"/>
<entry key="passwordCallbackRef" value-ref="clientPasswordCallback"/>
</map>
</constructor-arg>
</bean>
<jaxws:client id="helloService"
serviceClass="demo.ws.soap_spring_cxf_wss4j.HelloService"
address="http://localhost:8080/Spring-CXF/ws/soap/hello">
<jaxws:outInterceptors>
<ref bean="wss4jOutInterceptor"/>
</jaxws:outInterceptors>
</jaxws:client>
客户端密码回调处理器
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];
callback.setPassword("clientpass");
}
}
4.调用 WS 并观察控制台日志
<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-eafcd726-f2e5-4f60-8345-020fa984d2b6">
<wsse:Username>yuruixin</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:say xmlns:ns2="http://c.cxf.com/">
<arg0>springClient</arg0>
</ns2:say>
</soap:Body>
</soap:Envelope>
你看看,这就加密完成了。然而你把密码给我暴露出来几个意思 ?给我把密码给成密文。其实,so easy,只需要将”passwordType” value设为”PasswordDigest”就ok啦!记住!server端和client端都得改啊!!!效果如下:
<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-d4c6be41-95c0-4248-8539-52d7e35f7cfd">
<wsse:Username>yuruixin</wsse:Username>
<wsse:Password
Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">uClk/hBYPEPWBQP/sobuBcALTpk=</wsse:Password>
<wsse:Nonce
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">uPjtpWLO5kVx1KNXS5dVtg==</wsse:Nonce>
<wsu:Created>2016-12-15T03:13:10.690Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<soap:Body>
<ns2:say xmlns:ns2="http://c.cxf.com/">
<arg0>springClient</arg0>
</ns2:say>
</soap:Body>
</soap:Envelope>
第二种:基于数字签名的身份认证
1.生成密钥库
创建一个名为 keystore.bat 的批处理文件
运行该批处理程序,将生成两个文件:server_store.jks 与 client_store.jks,随后将 server_store.jks 放入服务端的 classpath 下,将 client_store.jks 放入客户端的 classpath 下。
@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
2.完成服务端 CXF 相关配置
<bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<!-- 验签(使用对方的公钥) -->
<!-- action 为 Signature -->
<entry key="action" value="Signature"/>
<entry key="signaturePropFile" value="server.properties"/>
</map>
</constructor-arg>
</bean>
server.priperties
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
3.完成客户端 CXF 相关配置
<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>
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
跟到这里出现问题了。。。。。 日后解决吧