Webservice架构设计

关于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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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服务类配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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.java
1
2
3
4
5
6
7
8
9
10
11
12
public class AuthPrepareProcesser4Client implements CallbackHandler {
@Override
public 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的客户端配置代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<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方法代码片断,客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值