CXF+WS-Security+Spring WebService服务器端+客户端及注意有关问题

CXF+WS-Security+Spring WebService服务器端+客户端及注意问题

项目中要用到webservice,刚听到的时候还挺开心的,因为我之前接触过,想来应该不是很难,CXF+WS-Security+Spring WebService服务器端+客户端及注意有关问题

谁料,事实不是这样的....CXF+WS-Security+Spring WebService服务器端+客户端及注意有关问题,让我费了个好劲啊。

不说了,下面上代码,这是入门级的,所以会比较详细,仔细看:


服务器端:

       1、接口+实现类

       

//接口
package com.ekservice.service;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

import com.ek.entry.user.User;

/** 
 * @author Administrator 
 * 类说明 
 */
@WebService(targetNamespace="http://jeeek-dp/", name="ExampleService")
public interface ExampleService {

	@WebResult(name="password")
	@WebMethod(action="http://jeeek-dp/ExampleService/getStr")
	public User getStr(@WebParam(name="name")String userName);
}
//实现类
package com.ekservice.impl;

import javax.jws.WebService;

import com.ek.entry.user.User;
import com.ekservice.service.ExampleService;

/** 
 * @author Administrator
 * 类说明 
 */
@WebService(targetNamespace="http://jeeek-dp/", serviceName="ExampleService", name="ExampleService")
public class ExampleServiceImpl implements ExampleService {

	public User getStr(String userName) {
		User user = new User();
		user.setAddress("北京市海淀区222号");
		user.setMobilePhone("010-1101111");
		return user;
	}

}
2、WS-Security 安全验证 - 过滤器

package com.ekservice.interceptor;

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;
import org.apache.ws.security.WSSecurityException;

/** 
 * @author Administrator
 * 类说明 
 */
public class ExampleServiceInterceptor implements CallbackHandler{

	private Map<String, String> passwords = new HashMap<String, String>();
	
	public ExampleServiceInterceptor() {
		passwords.put("admin", "password");//此处的对应的是验证信息-用户名+密码,必须与客户端一致才可验证通过
	}
	public void handle(Callback[] callbacks) throws IOException,
			UnsupportedCallbackException {
		
		for (int i = 0; i < callbacks.length; i++) {
			WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];
			String identifier = pc.getIdentifier();//用户名
			int usage = pc.getUsage();//验证方式
			if (usage == WSPasswordCallback.USERNAME_TOKEN) {// 密钥方式USERNAME_TOKEN
				if (!passwords.containsKey(identifier)) {
					try {
						throw new WSSecurityException("User not match - "+identifier);
					} catch (WSSecurityException e) {
						e.printStackTrace();
					}
				}
				// username token pwd...
				// ▲这里的值必须和客户端设的值相同,从cxf2.4.x后校验方式改为cxf内部实现校验,不必自己比较password是否相同
				// 请参考:http://cxf.apache.org/docs/24-migration-guide.html的Runtime
				// Changes片段
				pc.setPassword(passwords.get(identifier));// ▲【这里非常重要】▲
				// ▲PS 如果和客户端不同将抛出org.apache.ws.security.WSSecurityException:
				// The
				// security token could not be authenticated or
				// authorized异常,服务端会认为客户端为非法调用
			}else if (usage == WSPasswordCallback.SIGNATURE) {// 密钥方式SIGNATURE
				if (!passwords.containsKey(identifier)) {
					try {
						throw new WSSecurityException("User not match - "+identifier);
					} catch (WSSecurityException e) {
						e.printStackTrace();
					}
				}
				// set the password for client's keystore.keyPassword
				// ▲这里的值必须和客户端设的值相同,从cxf2.4.x后校验方式改为cxf内部实现校验,不必自己比较password是否相同;
				// 请参考:http://cxf.apache.org/docs/24-migration-guide.html的Runtime
				// Changes片段
				pc.setPassword(passwords.get(identifier));// //▲【这里非常重要】▲
				// ▲PS:如果和客户端不同将抛出org.apache.ws.security.WSSecurityException:The
				// security token could not be authenticated or
				// authorized异常,服务端会认为客户端为非法调用
			}
		}
	}

}
3、Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p" 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-3.0.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" />
	<import resource="classpath:/META-INF/cxf/cxf-extension-soap.xml" />

        <!-- 安全验证过滤器 -->
	<bean id="exampleServiceInterceptor" class="com.ekservice.interceptor.ExampleServiceInterceptor"></bean>
	<!-- 接口实现类 -->
	<bean id="exampleServiceImpl" class="com.ekservice.impl.ExampleServiceImpl" />
        <!-- 发布接口 spring+cxf -->
        <jaxws:endpoint implementor="#exampleServiceImpl" address="/ExampleService">
        <!-- 为接口添加安全验证过滤器 -->
        <jaxws:inInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
			<!-- 这个地方不知道为什么,浪费了我宝贵的时间,官方文档说CXF2.0.x需要加这个过滤器,2.1.x及以后版本可以不加,
                         但是我用的是CXF2.7.7不加SAAJInInterceptor过滤器报错-->
			<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
			<bean id="WSS4JInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
				<constructor-arg>
					<map>
						<entry key="action" value="UsernameToken"></entry>
						<entry key="passwordType" value="PasswordText"></entry>
						<!-- 设置密码类型为加密
						<entry key="passwordType" value="PasswordDigest" /> 
						-->
						<entry key="passwordCallbackClass" value="com.ekservice.interceptor.ExampleServiceInterceptor"></entry>
					</map>
				</constructor-arg>
			</bean>
		</jaxws:inInterceptors>
		<jaxws:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
		</jaxws:outInterceptors>
	</jaxws:endpoint>
	
</beans>


4、User.java

package com.ek.entry.user;

import java.io.Serializable;
import java.util.Date;

/**
 * @包名   ek.entry.user.po
 * @文件名 User.java
 * @版本 V 1.0
 */
public class User implements Serializable{
	private static final long serialVersionUID = 1L;
	private int id;
	private String userName;	//用户名
	private String passWord;	//密码
	private String address;
	private String mobilePhone;
	private Date entryTime;		//录入时间
	private Date updateTime;	//更新时间
	private String entryUserId;	//录入人ID
	private String validFlag;	//有效标识
	private Integer access;
	/**
	 * @return the userName
	 */
	public String getUserName() {
		return userName;
	}
	/**
	 * @param userName the userName to set
	 */
	public void setUserName(String userName) {
		this.userName = userName;
	}
	/**
	 * @return the passWord
	 */
	public String getPassWord() {
		return passWord;
	}
	/**
	 * @param passWord the passWord to set
	 */
	public void setPassWord(String passWord) {
		this.passWord = passWord;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public String getMobilePhone() {
		return mobilePhone;
	}
	public void setMobilePhone(String mobilePhone) {
		this.mobilePhone = mobilePhone;
	}
	public static long getSerialversionuid() {
		return serialVersionUID;
	}
	/**
	 * @return the entryTime
	 */
	public Date getEntryTime() {
		return entryTime;
	}
	/**
	 * @param entryTime the entryTime to set
	 */
	public void setEntryTime(Date entryTime) {
		this.entryTime = entryTime;
	}
	/**
	 * @return the updateTime
	 */
	public Date getUpdateTime() {
		return updateTime;
	}
	/**
	 * @param updateTime the updateTime to set
	 */
	public void setUpdateTime(Date updateTime) {
		this.updateTime = updateTime;
	}
	/**
	 * @return the entryUserId
	 */
	public String getEntryUserId() {
		return entryUserId;
	}
	/**
	 * @param entryUserId the entryUserId to set
	 */
	public void setEntryUserId(String entryUserId) {
		this.entryUserId = entryUserId;
	}
	/**
	 * @return the validFlag
	 */
	public String getValidFlag() {
		return validFlag;
	}
	/**
	 * @param validFlag the validFlag to set
	 */
	public void setValidFlag(String validFlag) {
		this.validFlag = validFlag;
	}
	
	/**
	 * @return the id
	 */
	public int getId() {
		return id;
	}
	/**
	 * @param id the id to set
	 */
	public void setId(int id) {
		this.id = id;
	}
	public User() {
		super();
		
		this.entryTime = new Date();
		this.updateTime = new Date();
		this.validFlag = "1";
	}
	/**
	 * @return the access
	 */
	public Integer getAccess() {
		return access;
	}
	/**
	 * @param access the access to set
	 */
	public void setAccess(Integer access) {
		this.access = access;
	}
	
}
至此,服务器端开发完成,启动tomcat即可访问接口,http://localhost:8080/jeeek-dp/services/ExampleService?wsdl


客户端:

        1、使用wsdl2java命令生成客户端代码,只保留生成的javabean跟接口即可,其他的文件可以全部删去,CXF+WS-Security+Spring WebService服务器端+客户端及注意有关问题,我留下的文件。

         2、编写客户端接口访问验证拦截器,只要确保用户名密码与服务器相同即可(我的理解CXF+WS-Security+Spring WebService服务器端+客户端及注意有关问题),代码:

package com.ek.client.interceptor;

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;
import org.apache.ws.security.WSSecurityException;

/** 
 * @author 
 * 类说明 
 */
public class ExampleServiceClientInterceptor implements CallbackHandler{

	private Map<String, String> passwords = new HashMap<String, String>();
	
	public ExampleServiceClientInterceptor() {
		passwords.put("admin", "password");
	}
	public void handle(Callback[] callbacks) throws IOException,
			UnsupportedCallbackException {
		
		for (int i = 0; i < callbacks.length; i++) {
			WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];
			String identifier = pc.getIdentifier();
			int usage = pc.getUsage();
			
			if (usage == WSPasswordCallback.USERNAME_TOKEN) {// 密钥方式USERNAME_TOKEN
				System.out.println(passwords.containsKey(identifier)+"="+passwords.get(identifier)+"-"+identifier);
				if (!passwords.containsKey(identifier)) {
					try {
						throw new WSSecurityException("User not match - "+identifier);
					} catch (WSSecurityException e) {
						e.printStackTrace();
					}
				}
				pc.setPassword(passwords.get(identifier));// //▲【这里非常重要】▲
			}else if (usage == WSPasswordCallback.SIGNATURE) {// 密钥方式SIGNATURE
				if (!passwords.containsKey(identifier)) {
					try {
						throw new WSSecurityException("User not match - "+identifier);
					} catch (WSSecurityException e) {
						e.printStackTrace();
					}
				}
				pc.setPassword(passwords.get(identifier));// //▲【这里非常重要】▲
			}
		}
	}

}

3、客户端配置文件

<?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:jaxws="http://cxf.apache.org/jaxws"  
	xsi:schemaLocation="http://www.springframework.org/schema/beans   
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
		http://cxf.apache.org/jaxws   
		http://cxf.apache.org/schemas/jaxws.xsd">

	<bean id="exampleServiceClientInterceptor" 
		class="com.ek.client.interceptor.ExampleServiceClientInterceptor"></bean>
	
	<jaxws:client id="service"  
		address="http://localhost:8080/jeeek-dp/services/ExampleService"
		serviceClass="com.ek.client.services.example.ExampleService">
		<jaxws:outInterceptors>  
			<bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor"></bean>
			<bean id="WSS4JOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
				<constructor-arg>
					<map>
						<entry key="action" value="UsernameToken" />
						<entry key="passwordType" value="PasswordText" />
						<entry key="user" value="admin" />
						<!-- <entry key="mustUnderstand" value="false"></entry> -->
						<entry key="passwordCallbackRef">
							<ref bean="exampleServiceClientInterceptor" />
						</entry>
					</map>
				</constructor-arg>
			</bean>
		</jaxws:outInterceptors>
	</jaxws:client>
	
</beans>

4、客户端调用接口:

package com.ek.client.call;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.ek.client.services.example.ExampleService;
import com.ek.client.services.example.User;

/** 
 * @author 
 * 类说明 
 */
public class ExampleServiceClient {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cxf-client.xml");
		ExampleService es = (ExampleService) ctx.getBean("service");
		
		User u = es.getStr("ssss");
		
		System.out.println(u.getAddress);
	}
}

整个项目的包目录结构就不截图了,配置文件新建个src 文件放到里面即可,也可以直接放到src目录下。


因为整这个东西费了很多时间,坐下记录。



共勉!