CXF涉及安全方面主要有三个途径:
- 最简单的方式是使用Http Basic Auth,就是WSS4J的UsernameToken实现方式,优点是简单易用,缺点是每次都会在MESSAGE里面传密码,安全性低。
- Transport level(传输层内)的实现Https。CXF samples里面有一个例子wsdl_first_https, 很详细的讲了怎么使用。
- 对MESSAGE进行加密和签名(encryption and signing),请参考官方教程。
先来看第一种实现方式,按照上一篇教程中的内容先配置好,然后根据下面顺序进行修改
1. 修改服务端spring配置文件
<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" />
<bean id="serverPasswordCallback" class="com.demo.cxf.callbacks.ServerPasswordCallback"></bean>
<bean id="serverWSS4JInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken Timestamp" />
<!-- MD5加密明文密码 -->
<entry key="passwordType" value="PasswordDigest" />
<entry key="passwordCallbackRef">
<ref bean="serverPasswordCallback" />
</entry>
</map>
</constructor-arg>
</bean>
<jaxws:endpoint id="helloWorld"
implementor="com.demo.cxf.helloword.impl.HelloWordImpl" address="/HelloWorld">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<ref bean="serverWSS4JInInterceptor"/>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
</jaxws:outInterceptors>
<jaxws:properties>
<entry key="mtom-enabled" value="true" />
</jaxws:properties>
</jaxws:endpoint>
</beans>
2. 创建服务端CallbackHandler
package com.demo.cxf.callbacks;
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.ws.security.WSPasswordCallback;
public class ServerPasswordCallback implements CallbackHandler {
Map<String, String> user = new HashMap<String, String>();
{
user.put("admin", "123");
user.put("su", "123");
}
@Override
public void handle(Callback[] callbacks) throws IOException,UnsupportedCallbackException
{
WSPasswordCallback wpc = (WSPasswordCallback) callbacks[0];
if (!user.containsKey(wpc.getIdentifier()))
{
throw new SecurityException("No Permission!");
}
/*
* 此处特别注意::
* WSPasswordCallback 的passwordType属性和password 属性都为null,
* 你只能获得用户名(identifier),
* 一般这里的逻辑是使用这个用户名到数据库中查询其密码,
* 然后再设置到password 属性,WSS4J 会自动比较客户端传来的值和你设置的这个值。
* 你可能会问为什么这里CXF 不把客户端提交的密码传入让我们在ServerPasswordCallbackHandler 中比较呢?
* 这是因为客户端提交过来的密码在SOAP 消息中已经被加密为MD5 的字符串,
* 如果我们要在回调方法中作比较,那么第一步要做的就是把服务端准备好的密码加密为MD5 字符串,
* 由于MD5 算法参数不同结果也会有差别,另外,这样的工作CXF 替我们完成不是更简单吗?
*/
wpc.setPassword(user.get(wpc.getIdentifier()));//如果包含用户名,就设置该用户名正确密码,由CXF验证密码
String username = wpc.getIdentifier();
String password = wpc.getPassword();
System.out.println("userName:" + username + " password:" + password);
}
}
3. 修改客户端spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<bean id="clientOutPasswordCallback" class="cxf.callbacks.ClientOutPasswordCallback"></bean>
<bean id="clientWSS4JOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken Timestamp" />
<!-- MD5加密明文密码 -->
<entry key="passwordType" value="PasswordDigest" />
<entry key="user" value="admin" />
<entry key="passwordCallbackRef">
<ref bean="clientOutPasswordCallback" />
</entry>
</map>
</constructor-arg>
</bean>
<jaxws:client id="helloClient" serviceClass="com.demo.cxf.helloword.HelloWord"
address="http://localhost:8080/webservice/services/HelloWorld">
<jaxws:outInterceptors>
<ref bean="clientWSS4JOutInterceptor"/>
</jaxws:outInterceptors>
</jaxws:client>
</beans>
4. 创建客户端CallbackHandler
package cxf.callbacks;
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.ws.security.WSPasswordCallback;
public class ClientOutPasswordCallback implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
if (callbacks != null && callbacks.length > 0) {
Callback callback = callbacks[0];
// 设置用户密码,供服务端验证,可以从配置文件或者数据库里面读取
WSPasswordCallback wsc = (WSPasswordCallback) callback;
wsc.setPassword("123");
}
}
}
5. 调用代码
ApplicationContext context = new ClassPathXmlApplicationContext(
"cxf/cxf-client.xml");
HelloWord helloWord = (HelloWord) context.getBean("helloClient");
System.out.println(helloWord.sayHello("Bruce"));