Acegi 为 Web services 的安全护航

来源:互联网 发布:淘宝网店没上货被收回 编辑:程序博客网 时间:2024/06/10 01:34

 原文网址:http://www.oschina.net/question/12_18986

安全,可是说是个老生常谈的话题,也可以说是个永恒的话题,无论是在那个领域,那个行业,安全都是非常的重要,写本文时正好看到一些矿难和香港游客 在印尼发生灾难的新闻,可见我们周围到处都需要安全保驾护航,尽管安全是相对的,但不采取任何措施来应对安全问题带来的挑战,其后果将是惨不忍睹的,因此 我们要重视安全性问题,无论是在生活中,还是在软件领域,毕竟生活幸福,使用的软件安全,都是我们希望的。闲话少说,下面我们就针对 Web Services 的安全问题逐一分析,讲解;首先我们要介绍下 Acegi(Spring Security)。

Acegi(Spring Security) 介绍

Acegi 是一个用于 Spring Framework 的安全框架,能够和目前流行的 Web 容器无缝集成。它使用了 Spring 的方式提供了安全和认证安全服务服务,包括使用 Bean Context,拦截器和面向接口的编程方式。因此,Acegi 安全系统 能够轻松地适用于复杂的安全需求。

Acegi 的出现是令软件开发人员兴奋,激动,欢呼的,用福音来形容一点不为过呀。解决了 Java EE 安全性编程模型下的众多弊端,比如:便携性差,因 Java EE 应用必须使用容器提供的一些专有描述符 ; 开发人员要理解大量的专有描述符也不是一件容易的事情,毕竟不同的容器的描述符存在一定的差别的,有时候就是写错了一点,都需要好长时间的调试才能得以解 决 ; 还有就是使用 Java EE 安全性编程模型的应用测试工作很难进行,尤其是在 TDD 流行的当下,不能方便,顺利的进行测试的开发可谓是一大弊端呀。Acegi 就是一名神医,将这些弊病统统解决,尤其是 Spring Security 的出现使应用 Acegi 更加方便,快捷,下面我们就进入 Spring Security 的旅程。

第一眼 Spring Security

准备环境

  1. 下载 spring-security-3.0.3.RELEASE.zip 包从 http://www.springsource.com/download/community 这个链接 ;
  2. 将 spring-security-samples-contacts-3.0.3.RELEASE.war 和 spring-security-samples-tutorial-3.0.3.RELEASE.war 复制到 apache-tomcat-6.0.18 的 webapps 下;

步骤 2 中的两个 war 包是 spring-security-3.0.3.RELEASE.zip 解压后包含的两个 Demo,下面我们就是通过这两个 Demo 来看看 Spring Security 给我们带来的快感,每个的 Demo 我们都会做详细的解释,以使读者能很清楚的了解 Spring Security 的工作过程。

用户场景

下面我们模拟下经常应用的场景,比如用户登录后可以操作业务方法,我们知道业务方法不是随意让用户可以调用的,有些业务方法很敏感,比如电信的计费方法, 涉及分成,结算等关键业务,所以需要有足够权限的用户才可以调用,因此我们通常在业务方法前加入一层来拦截用户请求,根据用户权限来判断是否可以访问。


图 1. 用户场景实例图

下面我们主要来研究下在 security 层是怎样实现业务方法保护的。

初识 Acegi(contacts Demo)

找到 TOMCAT_HOME 下的 bin 目录然后点击 startup.bat 来完成启动 tomcat 的操作,然后我们在浏览器中输入 http://localhost:8080/spring-security-samples-contacts-3.0.3.RELEASE URL, 我们就可以看到应用的主页了,从主页内容中我们能够知道这个 Demo 所应用的 Spring Security 的功能,比如:Remember-me 认证服务,form-based 认证,BASIC 认证和 Database-sourced security data 等。然后点击 Manage 超链接将会转到登录界面,在登录界面中列出了数据库中的所有用户信息,包括用户名,密码和是否可用字段。我们可以清楚的看到 Contacts Demo 使用的是 HTTP 表单认证,使用自定义 Web 表单页面来收集用户凭证信息的这一认证机制由于用户交互性友好等方面受到广大用户的青睐。大家应该很熟悉 HTTP BASIC 认证吧,下面我们就通过修改 applicationContext-security.xml 文件来体验一下这种认证方式,将 applicationContext-security.xml 文件中清单 3 列出的内容注释掉。


清单 1. 注释掉的内容

    <form-login login-page="/login.jsp"  authentication-failure-url="/login.jsp?login_error=1"/> 

 

注:上面的内容是指定了登录的页面,用户登录时将显示这个指定的页面的。

然后我们重新启动 tomcat 容器,输入 http://localhost:8080/spring-security-samples-contacts-3.0.3.RELEASE/hello.htm URL. 然后点击 Manage 超链接,显示的这个界面是由浏览器生成的,和 XP 系统登录界面很像吧。

另外一个 Demo spring-security-samples-tutorial-3.0.3.RELEASE.war 我们不做过多的解释了,有兴趣的读者可以自行研究下,如有疑问可以和我联系的。我们知道 Acegi 即可以保护 Web 资源,又可以保护服务层的业务方法,还可以保护领域对象。保护服务层的业务方法的这个功能和本文要讲解的 Web Service 密切相关,所以下面我们将通过一个例子来详细讲解下 Acegi 是怎样来保护服务层的业务方法的,以使大家有一个全面的理解。

Spring security(Acegi) 保护服务层的业务方法

首先新建一个 java project 命名为 acegi-method-project,然后新建一个接口命名为 EmployeeManager,接口内容如清单 2 所示:


清单 2. EmployeeManager.java 类代码

    package org.ibm.acegi.beans;  import java.util.List;  public interface EmployeeManager {  /**  * Delete employee by employee Id  * @param id  */     public void deleteEmployeeById(int id);      /**      * get all employees          * @param id      * @return List<Employee>      */     public List findAllEmployees();  }

 

可以看出我们需要两个方法 deleteEmployeeById 和 findAllEmployees, 然后新建一个实现类 implement 接口 EmployeeManager,命名为 EmployeeManagerImpl,代码内容如清单 4 所示:


清单 3. EmployeeManagerImpl.java 类代码

    package org.ibm.acegi.beans;  import java.util.ArrayList;import java.util.List;import org.apache.log4j.Logger;  public class EmployeeManagerImpl implements EmployeeManager { private static Logger logger = Logger.getLogger(EmployeeManagerImpl.class);   @Override  public void deleteEmployeeById(int id) { System.out.println("deleteEmployeeById()");  }    @Override    public List findAllEmployees() {   List list = new ArrayList();   list.add("findEmployeeById");   System.out.println("findEmployeeById()");  r   eturn list; }} 

 

方法 deleteEmployeeById 通过员工 Id 来做删除操作,这里我们只是打印一条信息而已,没有实现具体过程 ; 同样 findAllEmployees 方法也是打印一条信息。上面我们说过了,我们要做的是服务层的业务方法的保护,所以要添加清单 5 所示的内容到 applicationContext.xml,applicationContext.xml 中,大家都很熟悉吧? spring 的配置文件,这里不详细介绍了。


清单 4. MethodSecurityInterceptor 业务方法保护

       <bean id="employeeManagerSecurity"     class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">   <property name="validateConfigAttributes">   <value>true</value>  </property>   <property name="authenticationManager">   <ref bean="authenticationManager"/>   </property>    <property name="accessDecisionManager">    <ref bean="accessDecisionManager"/>    </property>  <property name="runAsManager">    <ref bean="runAsManager"/>     </property>  <property name="objectDefinitionSource">      <value>org.ibm.acegi.beans.EmployeeManager.delete*=ROLE_SUPERVISOR,RUN_AS_SERVER   org.ibm.acegi.beans.EmployeeManager.findAllEmployees =    ROLE_TELLER,ROLE_SUPERVISOR,RUN_AS_SERVER</value>     </property>       </bean>  

 

从上面我们可以看到 MethodSecurityInterceptor 拦截器暴露的 objectDefinitionSource 属性类似与 FilterSecurityInterceptor 过滤器暴露的 objectDefinitionSource 属性,不过前者是针对服务层的业务方法的,而后者针对的是 Web 资源,HTTP URL。我们来简单介绍下 objectDefinitionSource 属性的定义形式,“=”左边的以全包路径列出的为方法名,而“=”右边的内容代表了此方法需要的角色集合,我们需要用逗号来间隔多个角色。

我们再来看下上面需要的 authenticationManager 属性,内容见清单 6 所示:


清单 5. authenticationManager 内容

    <bean id="authenticationManager"   class="org.acegisecurity.providers.ProviderManager">   <property name="providers">    <list>     <ref local="daoAuthenticationProvider"/>   <bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">   <property name="key" value="changeThis"/>    </bean><beanclass    ="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">    <property name="key" value="changeThis"/>     </bean>  </list>       </property>       </bean> 

 

清单 6 的内容很关键,我们看到 ProviderManager 这个类,他是认证管理器的实现,可以配置多种认证管理源来认证用户的,通过 list 属性我们可以很容易的明白这一点。我们再来看一下这里面提供的一种认证源 daoAuthenticationProvider,内容见清单 7 所示:


清单 6. daoAuthenticationProvider 内容

    <bean id="daoAuthenticationProvider"  class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">    <property name="userDetailsService"     ref="userDetailsService"/>       <property name="userCache">   <bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache"> <property name="cache">   <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">   <property name="cacheManager">   <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>   </property>   <property name="cacheName"   value="userCache"/>    </bean>     </property>     </bean>      </property>       </bean> 

 

userDetailsService 属性也是一个很关键的,详细配置见清单 8 所示:


清单 7. userDetailsService 内容

    <bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl"> <property name="userProperties">    <bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">  <property name="location"value="users.properties"/>    </bean></property>    </bean>  

 

它提供用户的核心信息,包括用户名,用户密码,账户状态等信息,这是用户信息的入口。

单元测试 - 保护业务方法

对于一个开发者来说,单元测试永远是必不可少的,尤其是在 TDD 流行的今天来说,下面我们新建一个测试类命名为 EmployeeManagerImplTest 继承自TestCase, 然后加入一个方法用来设置认证源命名为 createSecureContext,源码内容见清单 10 所示:


清单 8. createSecureContext 内容

    private static void createSecureContext(final BeanFactory bf,   final String username, final String password) {     AuthenticationProvider provider     = (AuthenticationProvider) bf.getBean("daoAuthenticationProvider");       Authentication auth   = provider.authenticate(     new UsernamePasswordAuthenticationToken(username, password));        SecurityContextHolder.getContext().setAuthentication(auth);       } 

 

这个方法提供了需要验证的基础信息,包括用户名,密码。

单元测试类的 teardown 方法内容如清单 11 所示:


清单 9. teardown 内容

        public void teardown() {           SecurityContextHolder.setContext(new SecurityContextImpl());       } 

 

加入我们需要测试类 EmployeeManagerImpl 的本次需要测试的方法,代码内容见清单 12 所示:


清单 10. EmployeeManagerImpl 的测试方法

   /**  * test deleteEmployeeById method.  */  public void testDeleteEmployeeById() {    EmployeeManager employeeManager =    (EmployeeManager) factory.getBean("employeeManager");     createSecureContext(factory, "administrator", "administrator");     employeeManager.deleteEmployeeById(1011);       }      /**   * test findAllEmployees method.   */  public void testFindAllEmployees() {   EmployeeManager employeeManager =  (EmployeeManager) factory.getBean("employeeManager");      createSecureContext(factory, "sale_user", "sale_user");         employeeManager.findAllEmployees();       }  

 

测试类 EmployeeManagerImplTest 依赖的泪如清单 13 所示:


清单 11. 测试类 EmployeeManagerImplTest 需要导入的依赖类

    import junit.framework.TestCase;  import org.acegisecurity.Authentication;  import org.acegisecurity.context.SecurityContextHolder;  import org.acegisecurity.context.SecurityContextImpl;  import org.acegisecurity.providers.AuthenticationProvider;  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;  import org.ibm.acegi.beans.EmployeeManager;  import org.springframework.beans.factory.BeanFactory;  import org.springframework.beans.factory.xml.XmlBeanFactory;  import org.springframework.core.io.ClassPathResource;  import org.springframework.core.io.Resource; 

 

至此我们的测试类全部完成了,我们可以运行该类,看一下效果,右键然后选择 Run as 然后选择 Junit test,result 见清单 14 所示:


清单 12. 测试结果

    Test-begin  test deleteEmployeeById() method!  test findEmployeeById() method!  ---sucess----  Test-end 

 

重写部分方法 - 了解整个工作过程

为了使大家更清楚的了解 Acegi 的工作过程,我们重写了 AfterInvocationProvider 和 ApplicationEventPublisher 这两个接口的方法,新建两个类,AfterInvocationProviderImpl 实现了 AfterInvocationProvider 接口,ApplicationEventPublisherImpl 实现了 ApplicationEventPublisher 接口,代码内容分别见清单 15 和清单 16 所示:


清单 13. AfterInvocationProviderImpl 类代码

    public class AfterInvocationProviderImpl implements AfterInvocationProvider {   private static Logger logger = Logger  .getLogger(AfterInvocationProviderImpl.class);   @Override   public boolean supports(ConfigAttribute attribute) {    System.out.println("ConfigAttribute value is: "    + attribute);   if (attribute.getAttribute().equals("EMPLOYEESECURITY_CUSTOMER"))   {return true;}return false;}  @Override  public boolean supports(Class clazz) {  System.out.println("Security Type: " + clazz);  if (clazz == MethodInvocation.class) {return true;}  return false;} 

 

可以看出我们重写了 supports 方法,如果属性值存在的话返回真,否则返回假。并打印一些信息。


清单 14. ApplicationEventPublisherImpl 类代码

    public class ApplicationEventPublisherImpl implements ApplicationEventPublisher {  private static Logger logger = Logger.getLogger(ApplicationEventPublisherImpl.class);   @Override  public void publishEvent(ApplicationEvent event) {  System.out.println("Security method: " + event);   }  } 

 

publishEvent 方法我们加入了一些输出信息,这样有助于我们更好的了解 Spring Security 的工作过程,以使我们能更好的控制它。

然后我们修改下 applicationContext.xml 文件,在 bean employeeManagerSecurity 中添加属性内容见清单 17 所示:


清单 15. applicationContext 文件修改的内容

    <property name="applicationEventPublisher">    <bean class="org.ibm.acegi.beans.ApplicationEventPublisherImpl"/>     </property>       <property name="afterInvocationManager">        <ref bean="afterInvocationManager"/>     </property> 

 

然后再添加一个 bean 名称为 afterInvocationManager,内容见清单 18 所示:


清单 16. afterInvocationManager bean 内容

    <bean id="afterInvocationManager"  class="org.acegisecurity.afterinvocation.AfterInvocationProviderManager">    <property name="providers">       <list>        <bean class="org.ibm.acegi.beans.AfterInvocationProviderImpl"/>       </list>      </property>     </bean> 

 

接下来我们在运行下单元测试文件 EmployeeManagerImplTest,运行结果如清单 19 所示:


清单 17. 重构后的单元测试运行结果

    Test-begin  Security Type: interface org.aopalliance.intercept.MethodInvocation  ConfigAttribute value is: EMPLOYEESECURITY_CUSTOMER  Security method: org.acegisecurity.event.authorization  .AuthorizedEvent[source=ReflectiveMethodInvocation:  public abstract void org.ibm.acegi.beans.EmployeeManager.deleteEmployeeById(int);  target is of class [org.ibm.acegi.beans.EmployeeManagerImpl]]  test deleteEmployeeById() method!  Security method: org.acegisecurity.event.authorization  .AuthorizedEvent[source=ReflectiveMethodInvocation:  public abstract java.util.List org.ibm.acegi.beans.EmployeeManager.findAllEmployees();  target is of class [org.ibm.acegi.beans.EmployeeManagerImpl]]  test findEmployeeById() method!  ---sucess----  Test-end 

 

从结果中我们能很清晰的知道 Acegi 的加载过程,有助于我们更好的理解,使用好 Acegi。通过上面的学习我们对 Spring security(Acegi) 有了一个初步的了解,并对其是怎样来对服务层的业务方法进行保护有了更清晰的认识,为我们接下来的知识讲解铺平了道路,下面我们就介绍下 Acegi 是怎样为 Web Service 提供保护的,首先我们新建一个 Web Service,我们知道开发 Web Service 的方法有很多种,我还是比较喜欢用 CXF 的。

开发 Web Services

新建一个 Java project 工程,命名为 ws _example, 创建 service 类,CXFService.java 和 CXFServiceImpl.java, 代码分别见清单 7 和清单 8 所示:


清单 18. CXFService.java

    package org.ibm.cxf.service;  public interface CXFService {  public String sayHello(String name);  } 



清单 19. CXFServiceImpl.java

    package org.ibm.cxf.service.impl;  import org.ibm.cxf.service.CXFService;  public class CXFServiceImpl implements CXFService{  public String sayHello(String name) {return "Hello "+name;}} 

 

接下来,我们要新建一个 cxf-servlet.xml 和 web.xml 文件,文件内容分别如清单 9 和清单 10 所示:


清单 20. cxf-servlet.xml

    <?xml version="1.0" encoding="UTF-8"?>  <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:simple="http://cxf.apache.org/simple" xmlns:soap="http://cxf.apache.org/bindings/soap" xsi:schemaLocation=" http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd  http://cxf.apache.org/bindings/soap  http://cxf.apache.org/schemas/configuration/soap.xsd  http://cxf.apache.org/simple  http://cxf.apache.org/schemas/simple.xsd">  <simple:server id="CXFservice" serviceClass="org.ibm.cxf.service.CXFService" address="/CXFService"><simple:serviceBean><bean  class="org.ibm.cxf.service.impl.CXFServiceImpl" />  </simple:serviceBean></simple:server></beans> 

 

注意一下,simple:server 后的 serviceClass 指定的是接口的名称而 simple:serviceBean 后指定的是实现类的名称,这个地方费了我好多时间呀,郁闷。


清单 21. web.xml

    <?xml version="1.0" encoding="UTF-8"?>  <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  "http://java.sun.com/dtd/web-app_2_3.dtd"><web-app>   <display-name>cxf</display-name>  <description>cxf</description><servlet>  <servlet-name>cxf</servlet-name>  <display-name>cxf</display-name>  <description>Apache CXF Endpoint</description><servlet-class>  org.apache.cxf.transport.servlet.CXFServlet</servlet-class>  <load-on-startup>1</load-on-startup></servlet>  <servlet-mapping><servlet-name>cxf</servlet-name>  <url-pattern>/services/*</url-pattern></servlet-mapping><session-config>  <session-timeout>30</session-timeout></session-config>  </web-app> 

 

上面代码配置了一个 servlet,名称为 cxf。

最后我们还是需要新建一个 ant 脚本来打包,build.xml,其脚本如清单 11 所示:


清单 22. build.xml for CXFService

    <project name="service" basedir="." default="war">  <property name="dist.dir" value="${basedir}/WEB-INF" />  <property name="dist.dir.classes"  value="${dist.dir}/classes" />  <path id="build.class.path">  <fileset dir="${basedir}/lib">  <include name="*.jar" /></fileset>  </path><target name="war" depends="">  <javac srcdir="src"  destdir="${dist.dir.classes}" includes="org/ibm/cxf/service/*/*">  <classpath refid="build.class.path" />  </javac><war destfile="${dist.dir}/cxf.war"   webxml="${dist.dir}/web.xml">  <classes dir="${dist.dir.classes}" />  <lib dir="${basedir}/lib" />  <webinf dir="${dist.dir}"><include name="cxf-servlet.xml" />  </webinf></war></target></project> 

 

运行 ant 脚本,将生成的 cxf.war 包放到 tomcat 的部署目录下,启动 tomcat,待启动完成后,我们在浏览器中输入 http://localhost:8080/cxf/services/CXFService?wsdl 将显示如下信息:

  <?xml version="1.0" encoding="UTF-8" ?>  -<wsdl:definitions name="CXFService" targetNamespace="http://service.cxf.ibm.org/" xmlns:ns1="http://schemas.xmlsoap.org/wsdl/soap/http"  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"  xmlns:tns="http://service.cxf.ibm.org/"   xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"   xmlns:xsd="http://www.w3.org/2001/XMLSchema">-<wsdl:types>-<xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://service.cxf.ibm.org/" xmlns:tns="http://service.cxf.ibm.org/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">  <xsd:element name="sayHello" type="tns:sayHello" /> ……  </wsdl:definitions> 

 

出现如上信息表示 service 发布成功了,关于 service 的详细信息在这个 WSDL 文件中都有定义,如出现问题请仔细检查以上步骤。

下面我们测试下 service 是否可用。

单元测试

新建一个 JUnit 测试类 CXFServiceTest,代码如清单 12 所示:


清单 23. CXFServiceTest.java

    package org.ibm.axis.service.client;  import junit.framework.Assert;  import org.apache.cxf.frontend.ClientProxyFactoryBean;  import org.ibm.cxf.service.CXFService;  import org.junit.Test;  public class CXFServiceTest {  @Test  public void testSayHello(){  ClientProxyFactoryBean factory = new ClientProxyFactoryBean();  factory.setServiceClass(CXFService.class);  factory.setAddress("http://localhost:8080/cxf/services/CXFService");  CXFService client = (CXFService) factory.create();  String response = client.sayHello("jack");  Assert.assertEquals("Hello jack", response);  }  } 

 

其中 Address 可以从上面的 WSDL 文件中得到。

运行 JUnit test, 结果如图 6 所示:


图 2. Junit service 测试结果

可见这个 Web Service 方法是可用的,对于这个 Web Service 的保护我们将采取拦截器的方式。

对于 Interceptor 更多知识请大家自行查看相关资料,下面我们新建一个拦截器 SecurityInInterceptor 继承自 AbstractPhaseInterceptor<Message> 类,代码见清单 26 所示:


清单 24. SecurityInInterceptor 拦截器代码

    public class SecurityInInterceptor extends AbstractPhaseInterceptor<Message> {  private static Log logger = LogFactory.getLog(SecurityInInterceptor.class);  private AuthenticationManager authenticationManager;  public void setAuthenticationManager(  AuthenticationManager authenticationManager) {  this.authenticationManager = authenticationManager;  }  public SecurityInInterceptor() {  super(Phase.INVOKE);}  public void handleMessage(Message message) throws Fault {  String baseAuth = null;  Map<String, List<String>> reqHeaders = CastUtils  .cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));  if (reqHeaders != null) {  for (Map.Entry<String, List<String>> e : reqHeaders.entrySet()) {   if ("Authorization".equalsIgnoreCase(e.getKey()))  baseAuth = e.getValue().get(0);}   }   if ((baseAuth != null) && baseAuth.startsWith("Basic ")) {  byte[] base64Token;String username = "";String password = "";  try {            base64Token = baseAuth.substring(6).getBytes("UTF-8");            String token=new String(Base64.decodeBase64(base64Token),"UTF-8");              int delim = token.indexOf(":");             if (delim != -1)              {username = token.substring(0, delim);              password = token.substring(delim + 1);}              Authentication authResult =              authenticationManager.authenticate             (new UsernamePasswordAuthenticationToken(username, password));                 if(logger.isDebugEnabled()){logger.                debug("Authentication success:"+                 authResult.toString());}SecurityContextHolder.getContext()                .setAuthentication(authResult);}                catch (AuthenticationException failed)                {if (logger.isDebugEnabled())                {logger.debug("Authentication request for user '"                + username+ "' failed: " + failed.toString());}                SecurityContextHolder.clearContext();throw new Fault(failed);      } catch (Exception e){SecurityContextHolder.getContext().setAuthentication(null);  throw new Fault(e);}}}} 

 

再建一个 SecurityOutInterceptor 拦截器,代码见清单 26 所示:


清单 25. SecurityOutInterceptor 拦截器内容

    public class SecurityOutInterceptor extends AbstractPhaseInterceptor<Message> {  public SecurityOutInterceptor() {  super(Phase.SEND);}  public void handleMessage(Message message) throws Fault {  SecurityContextHolder.clearContext();}} 

 

然后我们修改 cxf-servlet.xml 文件,修改内容如清单 27 所示:


清单 26. 修改 cxf-servlet.xml 的内容

   <jaxws:endpoint id="CXFservice"  implementor="org.ibm.cxf.service.impl.CXFServiceImpl" address="/CXFService">    <jaxws:features>    <bean class="org.apache.cxf.feature.LoggingFeature" />    </jaxws:features>    <jaxws:inInterceptors>  <bean class="org.ibm.acegi.beans.security.SecurityInInterceptor">   <property name="authenticationManager" ref="authenticationManager" />   </bean> <bean class="org.ibm.acegi.beans.security.SecurityOutInterceptor" /> </jaxws:inInterceptors>  </jaxws:endpoint> 

 

这样当我们访问 Web Service 时候将会被这个拦截器拦截,我们看下 authenticationManager 这个属性很熟悉吧?对,我们上面讲解服务层的业务方法保护那部分提到了,最后根据applicationContext.xml 文件做相应的修改后,我们就可以使用 spring security(Acegi) 为我们开发的Web Service 方法提供保护了,以上对业务方法保护的过程我们可以用图 7 来简单概括下。


图 3. 业务保护方法时序图 mywang2011-03-22T11:17:00 图片顺序不对

总结

本文首先通过 Spring Security(Acegi) 自带的两个例子带领大家认识一下 Spring Security(Acegi) 的面貌,然后通过一个具体实例来讲解 Spring Security(Acegi) 是怎样来保护服务层的业务方法的,然后用 CXF 开发了一个 Web Service 并实现了 Spring Security(Acegi) 对它的保护。每一个实例都是通过从新建工程开始一步一步的带领大家来继续的,我们知道仅仅通过一片文章来很详细的将 Spring Security(Acegi) 保护 Web Services 的方方面面都阐述的很清楚,那是不可能的。

本文提供了最基本,最基础的开发过程,任何复杂的事务归根结底还是源于基础,有句话是这样说的,“授之以鱼,不如授之以渔”,我想只要方向对了,知道如何下手了,就不会有大的失误,