Webservice架构设计
来源:互联网 发布:淘宝做单一个月挣多少 编辑:程序博客网 时间:2024/06/06 19:08
关于j2ee中webservice的搭建以及不同系统中的访问,我已经在一篇博文《webservice之cxf实现》中进行了介绍,下面我们来谈谈webservice的架构设计要考虑的一些因素。
最首要的因素就是安全性,比如:如果验证调用者的合法身份?如果保证数据传输的安全性?等等。
先来看调用者身份的合法性验证问题。一般情况下ws底层使用http做为传输协议,http本身是无状态的,所以,我们要确保调用者的唯一身份,就要求调用者在调用时,携带身份标识参数。身份标识可以采用用户名加密码的方式实现,webservice标准中ws-security部分已经有了相应的标准。
第二个问题是数据传统过程中的安全性问题,服务器端和客户端如何知道信息的来源可靠、真实。其实这个问题在互联网应用中广泛存在的问题,并不是webservice考虑的范畴,但是,ws底层采用http协议传输,固然存在安全情况的问题(如果仅仅在公司内部局域网中的服务器群中调用,安全性要求就没有这么高了,呵呵)。
http验证信息的准确性,可采用的方式很多,比如:md5签名,时间戳、消息自身加密、https等等,下面我们先来看使用cxf如何对用户验证,并确保信息的安全性。
在服务器端,通过AuthCheckOnServer类对用户名密码进行验证,在配置com.my.webservice.WebServiceFacadeImpl的时候通过拦截器加入AuthCheckOnServer,注意,在action中加了Timestamp,而passwordType采用PasswordDigest,实现密码部分的加密(你可以通过抓包工具观察密码加密效果),服务器上拦截类代码如下:
AuthCheckOnServer.java
1234567891011121314
public class AuthCheckOnServer implements CallbackHandler { @Override public void handle(Callback[] callbackArray) throws IOException, UnsupportedCallbackException { if (callbackArray.length > 0) { WSPasswordCallback pc = (WSPasswordCallback)callbackArray[0]; String userId = pc.getIdentifier(); System.out.println("server得到用户名:" + userId ); String password = "此处根据用户标识userId,通过service(或dao)查询该用户的密码,我略去了..."; //设置好用查出的密码,此处是明文,cxf自动生成密文并进行校验 pc.setPassword(password); } }}
服务器ws服务类配置如下:
123456789101112131415
<jaxws:endpoint id="myWebService" address="/myWebService" implementor="com.my.webservice.WebServiceFacadeImpl"> <jaxws:inInterceptors> <bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" /> <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"> <constructor-arg> <map> <entry key="action" value="Timestamp UsernameToken" /> <entry key="passwordType" value="PasswordDigest" /> <entry key="passwordCallbackClass" value="com.my.webservice.AuthCheckOnServer" /> </map> </constructor-arg> </bean> </jaxws:inInterceptors> </jaxws:endpoint>
在cxf的客户端调用代码中,也加上拦截器,在调用请求中加入用户名及密码相关信息,客户端拦截类代码如下:
AuthPrepareProcesser4Client.java123456789101112
public class AuthPrepareProcesser4Client implements CallbackHandler {@Overridepublic void handle(Callback[] callbackArray) throws IOException, UnsupportedCallbackException { if (callbackArray.length > 0) { WSPasswordCallback pc = (WSPasswordCallback)callbackArray[0]; pc.setPassword("123"); pc.setIdentifier("yanwawa"); System.out.println("Client setting userName and password OK."); }}}
客户端spring的客户端配置代码如下:
1234567891011121314151617181920212223242526272829303132
<bean id="wsClient" class="com.my.webservice.IWebServiceFacade" factory-bean="wsClientProxy" factory-method="create" /> <bean id="wsClientProxy" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean"> <property name="serviceClass" value="com.my.webservice.IWebServiceFacade" /> <property name="address" value="http://localhost:8088/web_service_server/services/myWebService" /> <property name="inInterceptors"> <list> <ref bean="logIn" /> </list> </property> <property name="outInterceptors"> <list> <ref bean="logOut" /> <ref bean="saajOut" /> <ref bean="wss4jOut" /> </list> </property> </bean> <bean id="wss4jOut" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"> <constructor-arg> <map> <entry key="action" value="Timestamp UsernameToken" /> <entry key="user" value="yanwawa" /> <entry key="passwordType" value="PasswordDigest" /> <entry key="passwordCallbackClass" value="com.my.webservice.AuthPrepareProcesser4Client" /> </map> </constructor-arg> </bean> <bean id="logIn" class="org.apache.cxf.interceptor.LoggingInInterceptor" /> <bean id="logOut" class="org.apache.cxf.interceptor.LoggingOutInterceptor" /> <bean id="saajOut" class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />
当然,要以axis的soap客户端调用,代码要复杂一些,会用到WSSecEncrypt等几个类,也就是用这些类来帮助我们生成要发送的消息体,具体实现请参考axis的sample。
在传输过程中,为了确保数据的安全,我们可以在服务器上配置https支持,保证传输中的数据相对安全。
下面我给出另外一种不依赖ws-security的调用方式,当然,我是借鉴网上支付的数字签名的设计方式(网上支付实现请参阅《项目集成第三方支付设计方案》),即完全由程序自己来实现用户身份的验证,这种方案相对来说比较通用。
类图设计如下所示:
webservice sign架构图
在这种设计方案中,ws调用被封装到外观中,由于这种方式需要客户端上传用户id以及验证签名,所以外观类中的所有方法签名都带有这两个参数,似乎有了入侵,但话又说回来,这种方式并不影响service对象的方法设计,而且这种设计更有利于其它非java系统的ws调用。
非核心代码我都略去了,仅贴出客户端调用及服务器端验证的sayHello、add方法代码片断,客户端代码:
123456789101112131415
BeanFactory bf = new ClassPathXmlApplicationContext("classpath:ws-client.xml"); IWebServiceFacade wsClient = (IWebServiceFacade)bf.getBean("wsClient"); String clientId = "1"; String privateKey = "client1_md5_privateKey"; Map reqMap = new HashMap(); reqMap.put("name", "yanwawa"); String sign = SignatureHelper.sign(reqMap, privateKey); String sayHelloResult = wsClient.sayHello("yanwawa", clientId, sign); System.out.println(sayHelloResult); reqMap = new HashMap(); reqMap.put("number1", "9"); reqMap.put("number2", 2); sign = SignatureHelper.sign(reqMap, privateKey); int addResult = wsClient.add("9", 2, clientId, sign); System.out.println(addResult);
服务器端代码:
WebServiceFacadeImpl.java12345678910111213141516171819202122232425262728293031
@WebService(endpointInterface="com.my.webservice.IWebServiceFacade")public class WebServiceFacadeImpl implements IWebServiceFacade { //业务接口,由spring注入 private IBizService bizService; @Override public String sayHello(String name, String clientId, String signStr) { //auth check String privateKey = bizService.getClientPrivateKey(clientId); Map reqMap = new HashMap(); reqMap.put("name", name); if (SignatureHelper.verifySignCode(reqMap, privateKey, signStr)) { return bizService.sayHello(name); } else { //log and throws Exception throw new IllegalArgumentException("calling ws method 'sayHello', sign code error!"); } } public int add(String number1, int number2, String clientId, String signStr) { //auth check String privateKey = bizService.getClientPrivateKey(clientId); Map reqMap = new HashMap(); reqMap.put("number1", number1); reqMap.put("number2", number2); if (SignatureHelper.verifySignCode(reqMap, privateKey, signStr)) { return bizService.add(number1, number2); } else { //log and throws Exception throw new IllegalArgumentException("calling ws method 'add', sign code error!"); } }//……………………….其它代码略
总的来说,用第一种用户名加密码的方式确保用户的每一次调用的合法性在cxf中是不错的选择,因为它是在拦截器加入用户名及密码信息,服务器端的验证也是在拦截器中做的,因此,这种方式的代码看起来更简洁,可读性更好。
第二种方式类似于的线支付的签名验证方式,比较通用,但是方法签名都要携带验证信息,看起来比较累赘,但由于http的无状态性,身份标识每次都提交到服务器是一种比较简单的方法。第二种方式还有一个问题需要注意,就是客户端也要通过签名验证返回消息的合法性,
当然,任何东西都有两面性,好比一把“双刃剑”,我们只有根据项目的实际需求,平衡各种因素,扬长避短,选择最适合、最实用的一种方式,解决问题才是最终的目的。
0 0