目的
為遠(yuǎn)程服務(wù)調(diào)用提供統(tǒng)一的框架,該框架集中解決遠(yuǎn)程調(diào)用過程中的三方面問題:
a. 應(yīng)用透明性:應(yīng)用的接口和實現(xiàn)不依賴于框架的實現(xiàn)??蚣芸梢酝该鞯那袚Q各種遠(yuǎn)程調(diào)用技術(shù),而上層應(yīng)用的接口和實現(xiàn)不用做任何調(diào)整。
b. 安全性:安全性主要包括兩個方面:身份及簽名驗證(防篡改偽造);數(shù)據(jù)傳輸保密性(防監(jiān)聽);IP認(rèn)證。
c. 調(diào)用頻度控制:為保證服務(wù)可用,需要對于調(diào)用頻度根據(jù)一定的規(guī)則進(jìn)行控制。
實現(xiàn)技術(shù)
由于調(diào)用雙方都是基于Java的應(yīng)用,實現(xiàn)技術(shù)上建議采用基于Spring的Remoting框架,這樣可以實現(xiàn)應(yīng)用透明性,接口開發(fā)人員不用考慮遠(yuǎn)程調(diào)用等與業(yè)務(wù)無關(guān)的技術(shù)細(xì)節(jié)。基于Spring框架并進(jìn)行擴(kuò)展,我們可以在框架層次實現(xiàn)安全性和調(diào)用頻度限制。
由于調(diào)用雙方不在一個局域網(wǎng)環(huán)境內(nèi),因此在具體通訊協(xié)議上,最佳選擇即為Http。因此我們推薦的實現(xiàn)技術(shù)包括:Spring Remoting + Spring HttpInvoker,以及Spring Remoting + Hessian。
安全性包括身份驗證和數(shù)據(jù)傳輸安全兩個方面,身份驗證可以根據(jù)調(diào)用雙方的信任程度以及性能要求確定采用對稱加密或者非對稱加密,當(dāng)前提供了三種驗證措施,用戶名加密認(rèn)證,IP認(rèn)證,以及消息數(shù)字摘要加密驗證,該驗證可以在Spring Remoting基礎(chǔ)上進(jìn)行擴(kuò)展。數(shù)據(jù)傳輸安全則主要是擔(dān)心數(shù)據(jù)在傳輸過程中被截獲,對于基于Http的傳輸,使用Https即可(無需在框架或者應(yīng)用層支持)。
調(diào)用頻度控制,則可以應(yīng)用AOP技術(shù),對于調(diào)用進(jìn)行截獲和統(tǒng)計,根據(jù)一定的規(guī)則,判斷調(diào)用是否符合控制策略。
接口定義和實現(xiàn)規(guī)范
接口定義和實現(xiàn)為簡單的POJI和POJO即可,不過為了滿足遠(yuǎn)程調(diào)用的需要,需要保證所有參數(shù)和返回值都是可序列化的,另外,鑒于部分遠(yuǎn)程調(diào)用技術(shù)的序列化機制的特殊性(例如Hessian),數(shù)據(jù)類型應(yīng)盡可能簡單。此外,基于性能考慮,遠(yuǎn)程接口調(diào)用方式適用于中低頻度的小數(shù)據(jù)量的調(diào)用,對于大批量數(shù)據(jù)同步或者相當(dāng)高頻度的調(diào)用,遠(yuǎn)程接口調(diào)用方式并不合適。
設(shè)計實現(xiàn)
基本類圖
.gif)
圖1 遠(yuǎn)程服務(wù)發(fā)布類結(jié)構(gòu)圖
針對Hessian和HttpInvoker兩種遠(yuǎn)程服務(wù)調(diào)用的方式封裝了對于安全控制的兩個安全發(fā)布類,具體的安全配置以及安全操作都在RemoteContractTemplate中,這樣可以方便擴(kuò)展任何安全的需求變更,并且對原有任何的Exporter做了安全切面處理,防止過度耦合。
.gif)
圖 2 遠(yuǎn)程服務(wù)調(diào)用類結(jié)構(gòu)圖
遠(yuǎn)程服務(wù)調(diào)用對于不同的方法調(diào)用需要不同的定制,這里針對Hessian和HttpInvoker采用了替換植入內(nèi)部處理類的方式,Hessian植入了新的HessianProxyFactory用來生成新的HessianProxy來植入安全機制,HttpInvokerFactoryBean植入了新的HttpInvokerRequestExecutor來植入安全機制,同樣安全配置以及操作都封裝在RemoteContractTemplate中,集中控制和配置,方便擴(kuò)展和管理。
基本流程圖
圖 3 基本流程圖
如上圖所示,用戶發(fā)起請求調(diào)用遠(yuǎn)程服務(wù),首先是創(chuàng)建遠(yuǎn)程服務(wù)代理,然后通過植入安全信息將請求發(fā)送到遠(yuǎn)程服務(wù)發(fā)布處理類中,首先檢查安全信息,如果通過安全檢測就進(jìn)入方法調(diào)用攔截器中檢驗類似于頻率之類的限制過程中,通過攔截器的檢測就可以調(diào)用真正的遠(yuǎn)程服務(wù),并且獲得結(jié)果,將結(jié)果返回并封裝安全信息返回給服務(wù)調(diào)用代理,代理首先檢測是否有合法的安全信息,如果通過安全信息認(rèn)證,將結(jié)果返回給客戶端。
具體的配置和使用
這里通過一個Demo來說明如何使用這個遠(yuǎn)程服務(wù)調(diào)用框架。
假定一個售票管理服務(wù)要發(fā)布,售票管理服務(wù)結(jié)構(gòu)圖如下:
.gif)
圖 4 售票管理服務(wù)結(jié)構(gòu)圖
服務(wù)類接口為TicketManage,實現(xiàn)類是TicketManageImpl。測試調(diào)用類為TicketManageClient。接口和接口的實現(xiàn)類就是按照普通的Java規(guī)范來實現(xiàn)即可,TicketManageClient根據(jù)你選擇不同的服務(wù)調(diào)用方式來編寫代碼,這里用到了Hessian和HttpInvoker兩種方式,代碼如下:
public static void main(String[] args)
{
ApplicationContext ctx = new ClassPathXmlApplicationContext("ticket.xml");
TicketManage ticketManage = (TicketManage)ctx.getBean("ticketService");//hession調(diào)用的配置
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調(diào)用的配置
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配置文件,具體的內(nèi)容如下:
<?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">
<!—- 安全模版配置類,參數(shù)在后面會有詳細(xì)解釋 -->
<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>
<!—- 發(fā)布服務(wù)的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中藍(lán)色的內(nèi)容需要根據(jù)具體的情況作修改,黑色的內(nèi)容則不需要修改。
安全模版配置類參數(shù)具體說明:下面是代碼中的說明,大家一看就應(yīng)該明白了
private String encryptKeyPath;//encryptKey的路徑
private String decryptKeyPath;//decryptKey的路徑
private Key encryptKey;//根據(jù)encryptKey的路徑生成的key
private Key decryptKey;//根據(jù)decryptKey的路徑生成的key
private String algonrithm;//算法
private String needMD;//是否需要MD校驗
private String needUserAuth;//是否需要加密
private List<String> user;//允許對方訪問或者返回結(jié)果的用戶名
private String owner;//自己的簽名
private byte[] encrypted;//加密后的用戶簽名,一次加密,多次使用,增加性能提升
private String encoding = "GB2312";//編碼方式,加密和md的內(nèi)容的編碼方式
private String needIPAuth;//是否需要IP認(rèn)證
private List<String> ipList;//允許訪問的ip列表
private int readTimeout = 0;// 讀取數(shù)據(jù)超時時間設(shè)置,單位秒
private int connectTimeout = 0;// 連接超時時間設(shè)置,單位秒
客戶端設(shè)置好以后,就需要設(shè)置服務(wù)端了:
服務(wù)端很簡單首先是類似于Spring的Hessian和HttpInvoker調(diào)用的配置一樣,在WEB-INF/lib下面放入toolkit-sandbox-remoting.jar以及其它以來的jar,然后配置web.xml,增加如下內(nèi)容(這都是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內(nèi)容如下:
<?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"/><!—- 閥值代表次數(shù) -->
<property name="policy" value="hour"/><!—- 策略,當(dāng)前提供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發(fā)布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服務(wù)發(fā)布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,當(dāng)前key的算法有兩種,對稱加密和非對稱加密,分別用AES算法和RSA算法,注意一旦產(chǎn)生了Key文件,雙方就要使用相同的key文件,同一個用戶加解密的key文件可以不一致,但是雙方的解密和加密文件必須配對使用。
產(chǎn)生key文件的方法如下:
將toolkit-sandbox-remoting.jar所在的位置配置到classpath中,然后通過命令行執(zhí)行。
生成對稱加密key文件命令為:
Java com.alibaba.common.remoting.cryption.KeyGenTool genkey 文件目錄 文件名
生成非對稱加密key文件命令為:
Java com.alibaba.common.remoting.cryption.KeyGenTool genkeypair 文件目錄 文件名
這樣你分別會在文件目錄中找到對應(yīng)得文件,后綴名為.ky
|