最近一个项目预研,需要使用webservice,进行消息安全传输,客户要求包括加密和认证。我们使用的ws框架是cxf,认证是很容易做的,通过自定义实现一个Interceptor,就可以进行简单的用户和密码认证。但加密从来没有做过,在网上做了一些工作之后,发现cxf是可以通过wss4j实现签名、加密的。
根据网上的资料,做了一个demo,上传位置在:https://download.csdn.net/download/wangchsh2008/3539244 ,下载即可直接运行。
下面就过程,做一个简单说明。
WSS4J支持如下几种模式:
XML Security
XML Signature
XML Encryption
Tokens
Username Tokens
Timestamps
SAML Tokens
这里将使用Timestamps+Encryption+Signature组合。 关于wss4j的学习,请参考apache文档。
第一步是要生成服务端及客户端密钥文件,这里用到了JDK中的keytool工具,关于Keytool的使用以及参数说明,请读者自行学习相关文档。
本文中为了方便起见,已经将命令编辑为2个bat文件。在同一目录下,执行generateServerKey.bat批处理,即可生成clientStore.jks及serverStore.jks文件(clientKey.rsa和serverKey.rsa文件用不到)。
generateKeyPair.bat
rem @echo off
echo alias %1
echo keypass %2
echo keystoreName %3
echo KeyStorePass %4
echo keyName %5
echo keyName %5
keytool -genkey -alias %1 -keypass %2 -keystore %3 -storepass %4 -dname "cn=%1" -keyalg RSA
keytool -selfcert -alias %1 -keystore %3 -storepass %4 -keypass %2
keytool -export -alias %1 -file %5 -keystore %3 -storepass %4
generateServerKey.bat
call generateKeyPair.bat apmserver apmserverpass serverStore.jks keystorePass serverKey.rsa
call generateKeyPair.bat apmclient apmclientpass clientStore.jks keystorePass clientKey.rsa
keytool -import -alias apmserver -file serverKey.rsa -keystore clientStore.jks -storepass keystorePass -noprompt
keytool -import -alias apmclient -file clientKey.rsa -keystore serverStore.jks -storepass keystorePass -noprompt
生成的密钥文件中包含的信息:
服务端 账户:apmserver / apmserverpass
客户端 账户:apmclient / apmclientpass
如下图所示建立工程:
lib下的jar:
在服务器端,有一个对密码进行处理的类 PasswordHandler.java
package com.db.webservice.security;
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;
/**
* desc: comment PasswordHandler.java
* @author Chaisson(chengshengwang)
* @since Aug 19, 2011 5:08:30 PM
* @vision 1.0
*/
public class PasswordHandler implements CallbackHandler {
private Map passwords = new HashMap();
public PasswordHandler() {
passwords.put("apmserver", "apmserverpass");
passwords.put("apmclient", "apmclientpass");
}
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
String id = pc.getIdentifer();
pc.setPassword((String) passwords.get(id));
}
}
定义一个interface:TestService.java
package com.db.webservice;
import javax.jws.WebParam;
import javax.jws.WebService;
/**
* desc: comment TestService.java
* @author Chaisson(chengshengwang)
* @since May 13, 2011 11:41:03 AM
* @vision 1.0
*/
@WebService
public interface TestService {
public String sayHello(@WebParam(name="myName") String name);
public String printMan(User user);
}
实现上面的接口TestServiceImpl.java
package com.db.webservice.impl;
import com.db.webservice.TestService;
import com.db.webservice.User;
/**
* desc: comment TestServiceImpl.java
* @author Chaisson(chengshengwang)
* @since May 13, 2011 11:41:38 AM
* @vision 1.0
*/
public class TestServiceImpl implements TestService {
public String sayHello(String myName){
System.out.println("Hello World! "+myName);
return "SUCCESS";
}
public String printMan(User user) {
StringBuffer sb = new StringBuffer();
if(user.getAge()>=18 && user.getAge()<60){
sb.append("He is a young man. ");
}else if(user.getAge()>=60){
sb.append("He is an old man. ");
}else{
sb.append("He is a little boy. ");
}
if(user.getName()!=null){
sb.append(" His name is "+user.getName()+". ");
}
if(user.getDesc()!=null){
sb.append(" His description is that "+user.getDesc()+". ");
}
return sb.toString();
}
}
一个PO: User.java
package com.db.webservice;
/**
* desc: comment User.java
* @author Chaisson(chengshengwang)
* @since May 13, 2011 5:20:38 PM
* @vision 1.0
*/
public class User {
private int age;
private String name;
private String desc;
//省去setter/getter
}
还有3个配置文件:
server_insecurity_enc.properties
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=keystorePass
org.apache.ws.security.crypto.merlin.alias.password=apmserverpass
org.apache.ws.security.crypto.merlin.keystore.alias=apmserver
org.apache.ws.security.crypto.merlin.file=serverStore.jks
server_insecurity_sign.properties
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=keystorePass
#org.apache.ws.security.crypto.merlin.alias.password=apmserverpass
org.apache.ws.security.crypto.merlin.keystore.alias=apmserver
org.apache.ws.security.crypto.merlin.file=serverStore.jks
server_outsecurity_enc.properties
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=keystorePass
#org.apache.ws.security.crypto.merlin.alias.password=apmserverpass
#org.apache.ws.security.crypto.merlin.keystore.alias=apmserver
org.apache.ws.security.crypto.merlin.file=serverStore.jks
如果我没有看错的话,上面3个配置是一摸一样的,呵呵,仅仅是后面的有部分被注释了。
在客户端,我们首先需要一个Test类,TestServiceClient.java
package com.db.webservice.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import com.db.webservice.TestService;
import com.db.webservice.User;
/**
* desc: comment TestServiceClient.java
* @author Chaisson(chengshengwang)
* @since May 13, 2011 2:17:04 PM
* @vision 1.0
*/
public class TestServiceClient {
/**
* desc:
* @Chaisson(chengshengwang)
* @since May 13, 2011
* @version 1.0
* @param args
*/
public static void main(String[] args) {
/*JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean ();
factory.setServiceClass(TestService.class);
factory.setAddress("http://localhost:8080/TestCXF/services/MyService");
TestService service =(TestService)factory.create();
service.sayHello("Chaisson");
User user = new User();
user.setAge(10);
user.setName("Chaisson");
user.setDesc("He is a good man");
System.out.println(service.printMan(user));*/
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
TestService client = (TestService)ctx.getBean("client");
User user = new User();
user.setAge(10);
user.setName("Chaisson");
user.setDesc("He is a good man");
System.out.println(client.printMan(user));
}
}
当然也需要3个properties配置文件
insecurity_enc.properties
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=keystorePass
org.apache.ws.security.crypto.merlin.alias.password=apmclientpass
org.apache.ws.security.crypto.merlin.keystore.alias=apmclient
org.apache.ws.security.crypto.merlin.file=clientStore.jks
outsecurity_enc.properties
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=keystorePass
org.apache.ws.security.crypto.merlin.alias.password=apmclientpass
org.apache.ws.security.crypto.merlin.keystore.alias=apmclient
org.apache.ws.security.crypto.merlin.file=clientStore.jks
outsecurity_sign.properties
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=keystorePass
org.apache.ws.security.crypto.merlin.alias.password=apmclientpass
org.apache.ws.security.crypto.merlin.keystore.alias=apmclient
org.apache.ws.security.crypto.merlin.file=clientStore.jks
如果我没有看错的话,这回3个配置文件真的一摸一样了。
在客户端还需要加一个服务发布地址的配置文件
serverhost.properties
host.url=http://127.0.0.1:8080/TestCXF/services
最后整个工程少不了Spring配置文件:
applicationContext.xml
classpath:serverhost.properties
cxf的配置文件:
cxf-servlet.xml
还有一个web.xml,也简单提一下吧
cxfServlet
cxfServlet
cxfServlet
org.apache.cxf.transport.servlet.CXFServlet
2
cxfServlet
/services/*
contextConfigLocation
WEB-INF/cxf-servlet.xml
org.springframework.web.context.ContextLoaderListener
index.jsp
好了,project搭建完毕,在最后进行测试的时候,发生了很多问题。
如果你从http://download.csdn.net/source/3539244位置下载了我的demo,直接run的话,也是会报异常的。因为某些国家对加密算法有一定限制,所以SUN公司在发布JDK的时候里面的策略文件是限制版的。需要到SUN公司网站上下载非限制版的策略文件进行替换。
解决的办法,请参考我的另一篇文章http://blog.csdn.net/wangchsh2008/article/details/6708718
本文参考了以下一些文章:
http://blog.csdn.net/kunshan_shenbin/article/details/3813000
http://blog.myspace.cn/e/403951535.htm
http://cxf.apache.org/docs/ws-security.html