1. 程式人生 > >spring-remoting中的httpInvoker實現並安全驗證

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