spring-remoting中的httpInvoker實現並安全驗證
1. 簡單介紹httpInvoker的實現
Spring中,HTTPInvoker(HTTP呼叫器)是通過基於HTTP協議的分散式遠端呼叫解決方案.
1.1客戶端
a.向伺服器傳送遠端呼叫請求:遠端呼叫資訊——>封裝為遠端呼叫物件——>序列化寫入到遠端呼叫HTTP請求中——>向伺服器端傳送。
b.接收伺服器端返回的遠端呼叫結果:伺服器端返回的遠端呼叫結果HTTP響應——>反序列化為遠端呼叫結果物件。
1.2服務端
a. 接收客戶端傳送的遠端呼叫請求:客戶端傳送的遠端呼叫HTTP請求——>反序列化為遠端呼叫物件——>呼叫伺服器端目標物件的目標方法處理。
b.向客戶端返回遠端呼叫結果: 伺服器端目標物件方法的處理結果——>序列化寫入遠端呼叫結果HTTP響應中——>返回給客戶端。
2. 具體如何配置
2.1 客戶端配置
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans default-autowire="byName"> ...... <bean id="testService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"> <property name="serviceUrl" value="http://127.0.0.1:8300/testService.service"/> <property name="serviceInterface" value="com.xxx.biz.service.TestService"/> </bean> </beans>
2.2 服務端配置
<pre name="code" class="html"><?xml version="1.0" encoding="utf-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans default-autowire="byName"> .... <bean id="testService" class="com.xxxx.biz.service.impl.TestServiceImpl"></bean> <bean name="/testService.service" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="testService" /> <property name="serviceInterface" value="com.xxx.biz.service.TestService" /> </bean> </beans>
web.xml配置:
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>*.service</url-pattern>
</servlet-mapping>
2.3 呼叫測試
public class TestRemoting extends BaseUxiang {
private TestService testService;
public void testRemoting() {
long productId = 1224L;
ProductDO productDO = testService.queryById(productId);
System.out.println("productDO -> "+productDO);
if(productDO != null) {
System.out.println("info-->" + productDO.getId() + "," + productDO.getName());
}
}
public void setTestService(TestService testService) {
this.testService = testService;
}
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
/**
* @author niepeng
*
* @date 2012-9-4 上午11:08:48
*/
public class BaseUxiang extends AbstractTransactionalDataSourceSpringContextTests {
@Override
protected String[] getConfigLocations() {
// 當配置多個dataSource的時候,該行解決:No unique bean of type [javax.sql.DataSource]
super.setAutowireMode(AUTOWIRE_BY_NAME);
return new String[] {"applicationContext.xml"};
}
}
啟動服務端,然後客戶端執行testRemoting方法,能輸出對應的值。
3. 新增驗證
服務端部署一個安全的HTTP服務是件很容易的事情,新增一些限制條件就可以了。
客戶端實現上,正常來說也是比較簡單的,但是現在呼叫封裝是spring內部實現的,就拿HttpInvokerProxyFactoryBean來說,這個Bean可以由內建的JDK HTTP支援,也可由commons httpClient project來支援,內建的JDK HTTP不支援HTTP基本驗證,這意味著你需要使用HttpClient去訪問一個安全服務,配置HttpInvokerProxyFactoryBean,我們需要使用HttpClient的CommonsHttpInvokerRequestExecutor的例項中對httpInvokerRequestExecutor的特徵進行下一步設定這將事情變得複雜了。
CommonsHttpInvokerRequestExecutor不允許你將使用者名稱和密碼作為屬性設定,但是他確實允許你訪問HttpClient這個類所產生的例項,這是HttpClient project的核心。但是,你不可能使用Spring的依賴注入配置HttpClient的基本認證功能的憑證(因為不是setter/getter屬性),所以,我們使用了Spring的FactoryBean返回一個HttpClient的恰當配置的例項,我們用一個HttpClientFactoryBean類來完成HttpClient例項的裝配。
有了上面的分析,那麼實現就變得簡單了。
3.1 客戶端配置
<property name="httpInvokerRequestExecutor" ref="requestExecutor"/><?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC
"-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
......
<bean id="testService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="httpInvokerRequestExecutor" ref="requestExecutor"/>
<property name="serviceUrl" value="http://127.0.0.1:8300/testService.service"/>
<property name="serviceInterface" value="com.xxx.biz.service.TestService"/>
</bean>
<bean id="requestExecutor" class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor">
<property name="httpClient">
<bean class="com.xxxx.server.common.HttpClientFactoryBean">
<property name="username" value="abcdefgtest" />
<property name="password" value="testpsw1234" />
</bean>
</property>
</bean>
</beans>
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
public class HttpClientFactoryBean implements InitializingBean, FactoryBean {
private HttpClient httpClient;
private String username;
private String password;
private String authenticationHost;
public HttpClient getHttpClient() {
return httpClient;
}
public void setHttpClient(HttpClient httpClient) {
this.httpClient = httpClient;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getAuthenticationHost() {
return authenticationHost;
}
public void setAuthenticationHost(String authenticationHost) {
this.authenticationHost = authenticationHost;
}
public void afterPropertiesSet() throws Exception {
// 構造HttpClient物件
// httpClient = new HttpClient();
// 特別注意:這裡需要使用多執行緒,因為容器初始化後,每次請求都是通過這個httpclient請求去實現的,所以需要多執行緒
httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
// httpClient.getParams().setParameter("username", username);
// httpClient.getParams().setParameter("password", password);
// System.out.println("afterPropertiesSet->username=" + username + ", password=" + password);
httpClient.getState().setAuthenticationPreemptive(true);
Credentials credentials = new UsernamePasswordCredentials(username, password);
httpClient.getState().setCredentials(null, authenticationHost, credentials);
}
public Object getObject() throws Exception {
return httpClient;
}
public Class getObjectType() {
return HttpClient.class;
}
public boolean isSingleton() {
return true;
}
}
3.2 服務端配置
<pre name="code" class="html"><?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC
"-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
....
<bean id="testService" class="com.xxxx.biz.service.impl.TestServiceImpl"></bean>
<bean name="/testService.service" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="testService" />
<property name="serviceInterface" value="com.xxx.biz.service.TestService" />
</bean>
</beans>
3.3 呼叫測試
public class TestRemoting extends BaseUxiang {
private TestService testService;
public void testRemoting() {
long productId = 1224L;
ProductDO productDO = testService.queryById(productId);
System.out.println("productDO -> "+productDO);
if(productDO != null) {
System.out.println("info-->" + productDO.getId() + "," + productDO.getName());
}
}
public void setTestService(TestService testService) {
this.testService = testService;
}
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
/**
* @author niepeng
*
* @date 2012-9-4 上午11:08:48
*/
public class BaseUxiang extends AbstractTransactionalDataSourceSpringContextTests {
@Override
protected String[] getConfigLocations() {
// 當配置多個dataSource的時候,該行解決:No unique bean of type [javax.sql.DataSource]
super.setAutowireMode(AUTOWIRE_BY_NAME);
return new String[] {"applicationContext.xml"};
}
}
到當前為止,客戶端呼叫的時候,已經新增驗證資訊了,接下來服務端處理。
服務端新增驗證
這裡是通過filter來驗證。
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
public class AuthenticationFilter implements Filter {
private String userName;
private String psw;
private static final Logger log = LoggerFactory.getLogger(AuthenticationFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
userName = filterConfig.getInitParameter("userName");
psw = filterConfig.getInitParameter("psw");
log.error("init username and psw=" + userName + "," + psw);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String authorization = req.getHeader("authorization");
if (authorization == null) {
log.error("remoting method request authorization is null");
return;
}
if (!authorization.startsWith("Basic")) {
log.error("remoting method request authorization is not start with basic");
return;
}
authorization = authorization.substring(authorization.indexOf(' ') + 1);
BASE64Decoder decoder = new BASE64Decoder();
byte[] bytes = decoder.decodeBuffer(authorization);
authorization = new String(bytes);
String parseUserName = authorization.substring(0, authorization.indexOf(':'));
String parsePassword = authorization.substring(authorization.indexOf(':') + 1);
if (!userName.equals(parseUserName) || !psw.equals(parsePassword)) {
log.error("remoting method request authorization username or psw is not match");
return;
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
上面如果驗證不通過,直接寫 return,這裡可以按照自己的需要來處理。
配置web.xml
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>*.service</url-pattern>
</servlet-mapping>
<filter>
<filter-name>remotingFilter</filter-name>
<filter-class>com.xxxx.common.AuthenticationFilter</filter-class>
<init-param>
<param-name>userName</param-name>
<param-value>abcdefgtest</param-value>
</init-param>
<init-param>
<param-name>psw</param-name>
<param-value>testpsw1234</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>remotingFilter</filter-name>
<url-pattern>*.service</url-pattern>
</filter-mapping>
網上有人說通過tomcat自帶角色機制的驗證也能實現服務端部分。。。
4. 關於spring-remoting呼叫的介紹
詳見:http://blog.csdn.net/lsblsb/article/details/40040385
為Hessian方式呼叫加密簽名的安全機制 http://blog.csdn.net/luotangsha/article/details/6655555