目的
為遠程服務調用提供統一的框架,該框架集中解決遠程調用過程中的三方面問題:
a. 應用透明性:應用的接口和實現不依賴于框架的實現。框架可以透明的切換各種遠程調用技術,而上層應用的接口和實現不用做任何調整。
b. 安全性:安全性主要包括兩個方面:身份及簽名驗證(防篡改偽造);數據傳輸保密性(防監聽);IP認證。
c. 調用頻度控制:為保證服務可用,需要對于調用頻度根據一定的規則進行控制。
實現技術
由于調用雙方都是基于Java的應用,實現技術上建議采用基于Spring的Remoting框架,這樣可以實現應用透明性,接口開發人員不用考慮遠程調用等與業務無關的技術細節。基于Spring框架并進行擴展,我們可以在框架層次實現安全性和調用頻度限制。
由于調用雙方不在一個局域網環境內,因此在具體通訊協議上,最佳選擇即為Http。因此我們推薦的實現技術包括:Spring Remoting + Spring HttpInvoker,以及Spring Remoting + Hessian。
安全性包括身份驗證和數據傳輸安全兩個方面,身份驗證可以根據調用雙方的信任程度以及性能要求確定采用對稱加密或者非對稱加密,當前提供了三種驗證措施,用戶名加密認證,IP認證,以及消息數字摘要加密驗證,該驗證可以在Spring Remoting基礎上進行擴展。數據傳輸安全則主要是擔心數據在傳輸過程中被截獲,對于基于Http的傳輸,使用Https即可(無需在框架或者應用層支持)。
調用頻度控制,則可以應用AOP技術,對于調用進行截獲和統計,根據一定的規則,判斷調用是否符合控制策略。
接口定義和實現規范
接口定義和實現為簡單的POJI和POJO即可,不過為了滿足遠程調用的需要,需要保證所有參數和返回值都是可序列化的,另外,鑒于部分遠程調用技術的序列化機制的特殊性(例如Hessian),數據類型應盡可能簡單。此外,基于性能考慮,遠程接口調用方式適用于中低頻度的小數據量的調用,對于大批量數據同步或者相當高頻度的調用,遠程接口調用方式并不合適。
設計實現
基本類圖
.gif)
圖1 遠程服務發布類結構圖
針對Hessian和HttpInvoker兩種遠程服務調用的方式封裝了對于安全控制的兩個安全發布類,具體的安全配置以及安全操作都在RemoteContractTemplate中,這樣可以方便擴展任何安全的需求變更,并且對原有任何的Exporter做了安全切面處理,防止過度耦合。
.gif)
圖 2 遠程服務調用類結構圖
遠程服務調用對于不同的方法調用需要不同的定制,這里針對Hessian和HttpInvoker采用了替換植入內部處理類的方式,Hessian植入了新的HessianProxyFactory用來生成新的HessianProxy來植入安全機制,HttpInvokerFactoryBean植入了新的HttpInvokerRequestExecutor來植入安全機制,同樣安全配置以及操作都封裝在RemoteContractTemplate中,集中控制和配置,方便擴展和管理。
基本流程圖
圖 3 基本流程圖
如上圖所示,用戶發起請求調用遠程服務,首先是創建遠程服務代理,然后通過植入安全信息將請求發送到遠程服務發布處理類中,首先檢查安全信息,如果通過安全檢測就進入方法調用攔截器中檢驗類似于頻率之類的限制過程中,通過攔截器的檢測就可以調用真正的遠程服務,并且獲得結果,將結果返回并封裝安全信息返回給服務調用代理,代理首先檢測是否有合法的安全信息,如果通過安全信息認證,將結果返回給客戶端。
具體的配置和使用
這里通過一個Demo來說明如何使用這個遠程服務調用框架。
假定一個售票管理服務要發布,售票管理服務結構圖如下:
.gif)
圖 4 售票管理服務結構圖
服務類接口為TicketManage,實現類是TicketManageImpl。測試調用類為TicketManageClient。接口和接口的實現類就是按照普通的Java規范來實現即可,TicketManageClient根據你選擇不同的服務調用方式來編寫代碼,這里用到了Hessian和HttpInvoker兩種方式,代碼如下:
public static void main(String[] args)
{
ApplicationContext ctx = new ClassPathXmlApplicationContext("ticket.xml");
TicketManage ticketManage = (TicketManage)ctx.getBean("ticketService");//hession調用的配置
Ticket ticket = ticketManage.buyTicket(20);
System.out.println("ticket seat: " + ticket.getSeat());
int returncost = ticketManage.returnTicket(ticket);
System.out.println("return ticket, get back cost :" + returncost);
TicketManage httpTicketManage = (TicketManage)ctx.getBean("ticketHttpService");//HttpInvoke調用的配置
ticket = httpTicketManage.buyTicket(30);
System.out.println("ticket seat: " + ticket.getSeat());
returncost = httpTicketManage.returnTicket(ticket);
System.out.println("return ticket, get back cost :" + returncost);
}
ticket.xml是客戶端的spring配置文件,具體的內容如下:
<?xml version="1.0" encoding="GB2312"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
<!—- 安全模版配置類,參數在后面會有詳細解釋 -->
<bean id="remoteContractTemplate" class="com.alibaba.common.remoting.util.RemoteContractTemplate" init-method="init">
<property name="encryptKeyPath" value="file:c:\key.ky" />
<property name="decryptKeyPath" value="file:c:\key.ky" />
<property name="algonrithm" value="RSA"/>
<property name="needMD" value="true"/>
<property name="needUserAuth" value="true"/>
<property name="user">
<list>
<value>taobao</value>
<value>zhifubao</value>
<value>b2b</value>
</list>
</property>
<property name="owner" value="alisoft"/>
<property name="connectTimeout" value="6"/>
<property name="readTimeout" value="0"/>
<property name="needIPAuth" value="true"/>
<property name="ipList">
<list>
<value>10.0.26.23</value>
<value>10.0.0.42</value>
</list>
</property>
</bean>
<!—- 需要植入到HttpInvokerProxyFactoryBean的安全請求處理類 -->
<bean id="securityHttpInvokerRequestExecutor" class="com.alibaba.common.remoting.http.SecurityHttpInvokerRequestExecutor">
<property name="remoteContractTemplate" ref="remoteContractTemplate" />
</bean>
<!—- 發布服務的Bean -->
<bean id="ticketHttpService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:80/remote-examples/remote/TicketHttpService"/>
<property name="serviceInterface" value="com.alibaba.common.remoting.test.TicketManage"/>
<property name="httpInvokerRequestExecutor" ref="securityHttpInvokerRequestExecutor"/>
</bean>
<!—- Hessian的安全代理工廠類Bean -->
<bean id="securityHessianProxyFactory" class="com.alibaba.common.remoting.hessian.SecurityHessianProxyFactory">
<property name="remoteContractTemplate" ref="remoteContractTemplate" />
</bean>
<bean id="ticketService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:80/remote-examples/remote/TicketService"/>
<property name="serviceInterface" value="com.alibaba.common.remoting.test.TicketManage"/>
<property name="proxyFactory" ref="securityHessianProxyFactory"/>
</bean>
</beans>
上面的xml中藍色的內容需要根據具體的情況作修改,黑色的內容則不需要修改。
安全模版配置類參數具體說明:下面是代碼中的說明,大家一看就應該明白了
private String encryptKeyPath;//encryptKey的路徑
private String decryptKeyPath;//decryptKey的路徑
private Key encryptKey;//根據encryptKey的路徑生成的key
private Key decryptKey;//根據decryptKey的路徑生成的key
private String algonrithm;//算法
private String needMD;//是否需要MD校驗
private String needUserAuth;//是否需要加密
private List<String> user;//允許對方訪問或者返回結果的用戶名
private String owner;//自己的簽名
private byte[] encrypted;//加密后的用戶簽名,一次加密,多次使用,增加性能提升
private String encoding = "GB2312";//編碼方式,加密和md的內容的編碼方式
private String needIPAuth;//是否需要IP認證
private List<String> ipList;//允許訪問的ip列表
private int readTimeout = 0;// 讀取數據超時時間設置,單位秒
private int connectTimeout = 0;// 連接超時時間設置,單位秒
客戶端設置好以后,就需要設置服務端了:
服務端很簡單首先是類似于Spring的Hessian和HttpInvoker調用的配置一樣,在WEB-INF/lib下面放入toolkit-sandbox-remoting.jar以及其它以來的jar,然后配置web.xml,增加如下內容(這都是spring mvc框架的配置,大家可以參考spring來配置):
<servlet>
<servlet-name>remote</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remote</servlet-name>
<url-pattern>/remote/*</url-pattern>
</servlet-mapping>
然后在WEB-INF下面新建remote-servlet.xml,如果要換名稱,需要在上面的配置文件配置。
remote-servlet.xml內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sca="http://www.springframework.org/schema/sca"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/sca http://www.springframework.org/schema/sca/spring-sca.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd" default-autowire="byName">
<bean id="ticketManageTarget" class="com.alibaba.common.remoting.test.TicketManageImpl" />
<bean id="ticketManage" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.alibaba.common.remoting.test.TicketManage" />
<property name="target"><ref local="ticketManageTarget" /></property>
<!—- 攔截器bean -->
<property name="interceptorNames">
<list>
<value>remoteMethodInterceptor</value>
</list>
</property>
</bean>
<bean id="remoteMethodInterceptor" class="com.alibaba.common.remoting.interceptor.RemoteCounterInterceptor" >
<property name="valve" value="20"/><!—- 閥值代表次數 -->
<property name="policy" value="hour"/><!—- 策略,當前提供hour,day,month三種 -->
<!—- 上面的配置代表了每小時不能超過20次的訪問 -->
</bean>
<bean id="remoteContractTemplate" class="com.alibaba.common.remoting.util.RemoteContractTemplate" init-method="init">
<property name="encryptKeyPath" value="file:c:\key.ky" />
<property name="decryptKeyPath" value="file:c:\key.ky" />
<property name="algonrithm" value="RSA"/>
<property name="needMD" value="true"/>
<property name="needUserAuth" value="true"/>
<property name="user">
<list>
<value>alisoft</value>
<value>zhifubao</value>
<value>b2b</value>
</list>
</property>
<property name="owner" value="taobao"/>
<property name="connectTimeout" value="6"/>
<property name="readTimeout" value="0"/>
</bean>
<!—- HttpService發布Bean,植入了安全策略配置模版 -->
<bean name="/TicketHttpService" class="com.alibaba.common.remoting.http.SecurityHttpInvokerServiceExporter">
<property name="service" ref="ticketManage"/>
<property name="serviceInterface" value="com.alibaba.common.remoting.test.TicketManage"/>
<property name="remoteContractTemplate" ref="remoteContractTemplate" />
</bean>
<!—- Hessian服務發布Bean,植入了安全策略配置模版 -->
<bean name="/TicketService" class="org.springframework.remoting.caucho.SecurityHessianServiceExporter">
<property name="service" ref="ticketManage"/>
<property name="serviceInterface" value="com.alibaba.common.remoting.test.TicketManage"/>
<property name="remoteContractTemplate" ref="remoteContractTemplate" />
</bean>
</beans>
最后需要做的就是生成key,當前key的算法有兩種,對稱加密和非對稱加密,分別用AES算法和RSA算法,注意一旦產生了Key文件,雙方就要使用相同的key文件,同一個用戶加解密的key文件可以不一致,但是雙方的解密和加密文件必須配對使用。
產生key文件的方法如下:
將toolkit-sandbox-remoting.jar所在的位置配置到classpath中,然后通過命令行執行。
生成對稱加密key文件命令為:
Java com.alibaba.common.remoting.cryption.KeyGenTool genkey 文件目錄 文件名
生成非對稱加密key文件命令為:
Java com.alibaba.common.remoting.cryption.KeyGenTool genkeypair 文件目錄 文件名
這樣你分別會在文件目錄中找到對應得文件,后綴名為.ky
|