package
ch16.SecureHTTP;
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; private String authenticationRealm; 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 String getAuthenticationRealm() ... { return authenticationRealm; } public void setAuthenticationRealm(String authenticationRealm) ... { this .authenticationRealm = authenticationRealm; } public void afterPropertiesSet() throws Exception ... { // 构造HttpClient对象 httpClient = new HttpClient(); httpClient.getState().setAuthenticationPreemptive( true ); Credentials credentials = new UsernamePasswordCredentials(username,password); httpClient.getState().setCredentials(authenticationRealm,authenticationHost,credentials); } public Object getObject() throws Exception ... { return httpClient; } public Class getObjectType() ... { return HttpClient. class ; } public boolean isSingleton() ... { return true ; } }
我们在访问Spring HTTP 远程方法的时候,没有任何的安全机制,只要知道服务接口和WSDL,都可以访问,本文讲结合Tomcat的角色机制,为HTTP远程方法调用加入安全验证机制
服务端:
服务接口:
package
ch16.SecureHTTP;
public
interface
HelloWorld
...
{ public String getMessage(); }
package
ch16.SecureHTTP;
public
class
SimpleHelloWorld
implements
HelloWorld
...
{ public String getMessage() ... { return " hello world secure http " ; } }
配置文件:httpInvoker-servlet.xml(WEB-INF下)
<?
xml version="1.0" encoding="UTF-8"
?>
<
beans
xmlns
="http://www.springframework.org/schema/beans"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
>
<
bean
id
="defaultHandlerMapping"
class
="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"
>
</
bean
>
<
bean
name
="/helloWorld"
class
="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"
>
<
property
name
="service"
>
<
bean
class
="ch16.SecureHTTP.SimpleHelloWorld"
/>
</
property
>
<
property
name
="serviceInterface"
>
<
value
>
ch16.SecureHTTP.HelloWorld
</
value
>
</
property
>
</
bean
>
</
beans
>
web.xml
<
context-param
>
<
param-name
>
contextConfigLocation
</
param-name
>
<
param-value
>
/WEB-INF/httpInvoker-servlet.xml
</
param-value
>
</
context-param
>
<
servlet
>
<
servlet-name
>
context
</
servlet-name
>
<
servlet-class
>
org.springframework.web.context.ContextLoaderServlet
</
servlet-class
>
<
load-on-startup
>
1
</
load-on-startup
>
</
servlet
>
<
servlet
>
<
servlet-name
>
httpInvoker
</
servlet-name
>
<
servlet-class
>
org.springframework.web.servlet.DispatcherServlet
</
servlet-class
>
<
load-on-startup
>
3
</
load-on-startup
>
</
servlet
>
<
servlet-mapping
>
<
servlet-name
>
httpInvoker
</
servlet-name
>
<
url-pattern
>
/http/*
</
url-pattern
>
</
servlet-mapping
>
<!--
HTTP 安全性配置
-->
<
security-constraint
>
<
web-resource-collection
>
<
web-resource-name
>
Secure HTTP Services
</
web-resource-name
>
<
url-pattern
>
/http/helloWorld
</
url-pattern
>
<!--
spring配置文件中的bean name
-->
</
web-resource-collection
>
<
auth-constraint
>
<
role-name
>
manager
</
role-name
>
</
auth-constraint
>
</
security-constraint
>
<
login-config
>
<
auth-method
>
BASIC
</
auth-method
>
<
realm-name
>
ProSpringStudyWeb
</
realm-name
>
</
login-config
>
<
security-role
>
<
role-name
>
manager
</
role-name
>
</
security-role
>
主主要是的最后一段,这里使用了tomcat-user.xml文件中定义的manager角色作为验证,至于这个角色的用户名和密码,可以在tomcat-user.xml中配置,如下:
<?
xml version='1.0' encoding='utf-8'
?>
<
tomcat-users
>
<
role
rolename
="manager"
/>
<
role
rolename
="admin"
/>
<
user
username
="admin"
password
="1234"
roles
="admin,manager"
/>
</
tomcat-users
>
启动服务器,运行http://localhost:81/ProSpringStudyWeb/http/helloWorld (具体访问路径跟你你自己的project) 会出现一个要求输入用户名和密码的提示窗口,到此,服务端配置成功了,下面我们看客户端的实现
客户端:
部署一个安全的HTTP服务是件很容易的事情,而访问他就不那么简单了,就拿HttpInvokerProxyFactoryBean来说,这个Bean可以由内置的JDK HTTP支持,也可由commons httpClient project来支持,内置的JDK HTTP不支持HTTP基本验证,这意味着你需要使用HttpClient去访问一个安全服务,配置HttpInvokerProxyFactoryBean,我们需要使用HttpClient的CommonsHttpInvokerRequestExecutor的实例中对httpInvokerRequestExecutor的特征进行下一步设置
这将事情变得复杂了。CommonsHttpInvokerRequestExecutor不允许你将用户名和密码作为属性设置,但是他确实允许你访问HttpClient这个类所产生的实例,这是HttpClient project的核心。但是,你不可能使用Spring的依赖注入配置HttpClient的基本认证功能的凭证(因为不是setter/getter属性),所以,我们使用了Spring的FactoryBea返回一个HttpClient的恰当配置的实例,关于FactoryBean的介绍,请参考http://blog.csdn.net/daryl715/archive/2007/07/06/1681055.aspx ,我们用一个HttpClientFactoryBean类来完成HttpClient实例的装配
HttpClientFactoryBean
package
ch16.SecureHTTP;
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; private String authenticationRealm; 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 String getAuthenticationRealm() ... { return authenticationRealm; } public void setAuthenticationRealm(String authenticationRealm) ... { this .authenticationRealm = authenticationRealm; } public void afterPropertiesSet() throws Exception ... { // 构造HttpClient对象 httpClient = new HttpClient(); httpClient.getState().setAuthenticationPreemptive( true ); Credentials credentials = new UsernamePasswordCredentials(username,password); httpClient.getState().setCredentials(authenticationRealm,authenticationHost,credentials); } public Object getObject() throws Exception ... { return httpClient; } public Class getObjectType() ... { return HttpClient. class ; } public boolean isSingleton() ... { return true ; } }
配置文件:
<?
xml version="1.0" encoding="UTF-8"
?>
<
beans
xmlns
="http://www.springframework.org/schema/beans"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
>
<
bean
id
="helloWorldService"
class
="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"
>
<
property
name
="serviceUrl"
>
<
value
>
http://localhost:81/ProSpringStudyWeb/http/helloWorld
</
value
>
</
property
>
<
property
name
="serviceInterface"
>
<
value
>
ch16.SecureHTTP.HelloWorld
</
value
>
</
property
>
<
property
name
="httpInvokerRequestExecutor"
>
<
ref
bean
="requestExecutor"
/>
</
property
>
</
bean
>
<
bean
id
="requestExecutor"
class
="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor"
>
<
property
name
="httpClient"
>
<!--
使用Spring FactoryBean返回HttpClient实例,并定义好tomcat-user.xml中的正确用户名
-->
<
bean
class
="ch16.SecureHTTP.HttpClientFactoryBean"
>
<
property
name
="username"
>
<
value
>
admin
</
value
>
</
property
>
<
property
name
="password"
>
<
value
>
1234
</
value
>
</
property
>
</
bean
>
</
property
>
</
bean
>
</
beans
>
测试代码:
package
ch16.SecureHTTP;
import
java.io.IOException;
import
javax.servlet.ServletException;
import
javax.servlet.http.HttpServlet;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
public
class
SecureHttpClient
extends
HttpServlet
...
{ public static void main(String args[]) ... { ApplicationContext context = new ClassPathXmlApplicationContext( " ch16/SecureHTTP/applicationContext-client.xml " ); HelloWorld helloWorld = (HelloWorld)context.getBean( " helloWorldService " ); System.out.println(helloWorld.getMessage()); } }
如果配置文件中的用户名密码正确,则会打印出hello world secure http.如果用户名密码错误,则会出现以下异常
log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext). log4j:WARN Please initialize the log4j system properly. Exception in thread "main" org.springframework.remoting.RemoteAccessException: Cannot access HTTP invoker remote service at [http://localhost:81/ProSpringStudyWeb/http/helloWorld]; nested exception is org.apache.commons.httpclient.HttpException: Did not receive successful HTTP response: status code = 401, status message = [Unauthorized ] Caused by: org.apache.commons.httpclient.HttpException: Did not receive successful HTTP response: status code = 401, status message = [Unauthorized] at org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor.validateResponse(CommonsHttpInvokerRequestExecutor.java:183) at org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor.doExecuteRequest(CommonsHttpInvokerRequestExecutor.java:104) at org.springframework.remoting.httpinvoker.AbstractHttpInvokerRequestExecutor.executeRequest(AbstractHttpInvokerRequestExecutor.java:134) at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.executeRequest(HttpInvokerClientInterceptor.java:177) at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.invoke(HttpInvokerClientInterceptor.java:154) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204) at $Proxy0.getMessage(Unknown Source) at ch16.SecureHTTP.SecureHttpClient.main(SecureHttpClient.java:20)