SpringSide 对Acegi的扩展应用实例

来源:百度文库 编辑:神马文学网 时间:2024/06/13 02:19:09

 SpringSide 对Acegi的扩展应用实例

作者: cac,作者保留版权,转载请注明出处。

1. Acegi的不足之处

在上面Acegi关键组件详细描述的章节中讲述了FilterSecurityInterceptor和MethodSecurityInterceptor。这两个分别针对web和方法调用安全级别保护的Bean中,都需要在objectDefinitionSource属性里定义资源与权限的对应关系,也就是说这个对应关系必须写在XML里。

<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
      <property name="authenticationManager"><ref bean="authenticationManager"/>property>
      <property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/>property>
      <property name="objectDefinitionSource">
         <value>
                                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                                PATTERN_TYPE_APACHE_ANT
                                /index.jsp=ROLE_ANONYMOUS,ROLE_USER
                                /hello.htm=ROLE_ANONYMOUS,ROLE_USER
                                /logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
                                /switchuser.jsp=ROLE_SUPERVISOR
                                /j_acegi_switch_user=ROLE_SUPERVISOR
                                /acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER
                                /**=ROLE_USER
         value>
      property>
bean>

<bean id="securityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
       <property name="validateConfigAttributes"><value>falsevalue>property>
       <property name="authenticationManager"><ref bean="authenticationManager"/>property>
       <property name="accessDecisionManager"><ref bean="businessAccessDecisionManager"/>property>
       <property name="afterInvocationManager"><ref bean="afterInvocationManager"/>property>
       <property name="objectDefinitionSource">
         <value>
           sample.contact.ContactManager.create=ROLE_USER
            sample.contact.ContactManager.getAllRecipients=ROLE_ADMIN
         value>
bean>

当资源或权限改变的时候,就需要直接去更改源代码中的xml配置文件,而且需要去关闭容器重启项目,重新读取对应关系。这种操作在实际项目中是不能容许的,在实际项目中不允许重启项目,不允许修改源代码,需要有界面去远程有效方便地管理权限。

 另外,Acegi 中提供的 Tag 要求把授权写在tag中,这明显也不符合实际开发的需求

2. 改进方法

SpringSide的Acegi扩展应用实例在SpringSide目录下的 sandbox\security(没有这个目录)。 该实例提供了完整而简洁的应用管理界面,应用示范,你可以在这基础进行自己管理系统的扩展。也可以在这基础上直接使用。
请运行SpringSide 目录下的springside.bat就可以自动编译和启动项目了,注意期间需要输入你的tomcat安装路径。

1) 用数据库来管理用户,权限和资源及它们之间的对应关系

建立用户,权限和资源表,并建立对照表,如下:

用户表:USERS(PASSWD,NAME,STATUS), 其各列分别对应UserDetails接口中的password,username,enabled

权限表:ROLES(NAME), 其Name列对应的就是权限的名称

用户权限对照表:USER_ROLE(USER_ID,ROLE_ID), 某用户的权限对应的就是UserDetails接口中GrantedAuthority数组

资源表:RESOURCES(RES_TYPE,RES_STRING), 对应SpringSide的ResourceDetails接口的资源类型和资源串

权限资源对照表:ROLE_RESC(RESC_ID,ROLE_ID), 某资源对应的权限就是ResourceDetails接口中的GrantedAuthority数组

2) 从数据库读取各对应关系并转化为对象

定义AuthenticationService接口, 有getUsers()方法,返回一组UserDetails,getResources()方法返回一组ResourceDetails。
DaoAuthenticationService是AuthenticationService的实现,通过数据库查询数据来组装并返回UserDetails和ResourceDetails对象组。

<bean id="authenticationService" class="org.springside.components.acegi.service.DaoAuthenticationService">
        <property name="dataSource" ref="dataSource" />
       <property name="loadUsersQuery">
              <value>select name,passwd,status from ss_usersvalue>
       property>
       <property name="authoritiesByUsernameQuery">
              <value>
              select r.name from ss_users u,ss_roles r,ss_user_role ur where u.id = ur.user_id and r.id = ur.role_id and u.name = ?
              value>
       property>
       <property name="loadResourcesQuery">
              <value>select res_string, res_type from ss_resourcesvalue>
       property>
       <property name="authoritiesByResourceQuery">
              <value>
              select r.name from ss_resources c,ss_roles r,ss_role_resc rc where c.id = rc.resc_id and r.id =rc.role_id and c.res_string = ?
              value>
       property>
bean>

3) 缓存对象,加快权限认证速度

 如果每次都从数据库中读取资源信息再进行权限认证,则需要频繁访问数据库。使用缓存来保存权限认证信息是个比较好的解决方案。

AcegiCacheManagerFactoryBean是以Factory模式生成AcegiCacheManager实例。通过authenticationService获取UserDetails和ResourceDetails对象组,并放入userCache和resourceCache两个缓存中,由AcegiCacheManager来统一管理两个缓存。<bean id="acegiCacheManager" class="org.springside.components.acegi.cache.AcegiCacheManagerFactoryBean">
       <property name="userCache" ref="userCache" />
       <property name="resourceCache" ref="resourceCache" />
       <property name="authenticationService" ref="authenticationService" />
bean>

4) 用户访问资源需进行认证时,从缓存中读取权限信息

从上面可以看到, Acegi提供的两个拦截器FilterSecurityInterceptor和MethodSecurityInterceptor都是从objectDefinitionSource中的Value读取出权限信息的。所以我们在这扩展了这两个拦截器,CacheBaseUrlDefinitionSource和CacheBaseMethodDefinitionSource都实现了ObjectDefinitionSource接口的getAttributes(object)方法,让它们能通过acegiCacheManager从缓存中读取权限信息。 从cache中获取Url资源信息->
<bean id="filterDefinitionSource" class="org.springside.components.acegi.intercept.web.CacheBaseUrlDefinitionSource">
       <property name="convertUrlToLowercaseBeforeComparison" value="true" />
       <property name="useAntPath" value="true" />
       <property name="acegiCacheManager" ref="acegiCacheManager" />
bean>从cache中获取Method资源信息->
<bean id="objectDefinitionSource" class="org.springside.components.acegi.intercept.method.CacheBaseMethodDefinitionSource">
       <property name="acegiCacheManager" ref="acegiCacheManager" />
bean>

5) 修改权限的时,需要同时修改数据库和缓存

acegiCacheManager中提供了许多直接的方法可以修改缓存,具体请参考Springside的应用实例

6) 加入Tag读取缓存中的权限

在Jsp 页面上加入 

<%@ taglib prefix="ss" uri="http://www.springside.org.cn/taglibs" %>

以security中的menu作例子,在资源中加入权限"menu.resource",并赋给admin:

  
   


  •     Resc Management
       

  •   

    则只有admin登陆时可以显示资源管理菜单,employee登陆时因没有授权而无法显示

    3. 应用实例相关技术指导

    1) Extremetable with DWR

    应用实例里的表格显示和授权操作都使用了 Extreametable + Ajax 实现。

    Extremetable with ajax 不再像以前那样子通过 tag lib 来形成页面。 这里有  Lucky 写的翻译的一篇详细说明 extremeComponents使用AJAX 指南 。

    在该应用实例中和Extremetable Demo稍有不同:

    1. 使用 Spring Bean 来作为接口而不是重新new 一个类, 详见 WEB-INF/dwr.xml

    <dwr>
        <allow>
            <create creator="spring" javascript="UserManager">
                <param name="beanName" value="userManager"/>
            create>
    ......

    2.因每个table写生成代码的Assembler的代码相同的代码抽象出来,写成基类AbstractAssembler,每个子类之需要实现initCloumns(),getExportFileName(),getTableInvokeScriptMethodName(),getTableTitle()方法即可。
    addColumn提供多个重载方法,可以根据需要来加入相关属性,列名和是否可排序等。如果需要实现自己特殊的Cell,则需继承AbstractCell的基础上实现getCellValue方法,如UserStatesCell就是读取了参数中文显示用户状态的。

    以user table Assembler为例子:

    public class UserTableAssembler extends AbstractAssembler
    public void initCloumns() {
        addColumn("name");
        addColumn("email");
        Column columnStatus = addColumn("status");
        columnStatus.setCell(UserStatesCell.class.getName());
        addColumn("descn");
        addOperateColumn("id");
    }
    protected String getExportFileName() 
        return "users information.xls";
    }
    protected String getTableInvokeScriptMethodName() {
        return "buildUserTable()";
    }
    protected String getTableTitle() {
        return "User List";
    }
    }
    public class UserStatesCell extends AbstractCell {
    protected String getCellValue(TableModel table, Column column) {
        Integer state = (Integer)column.getPropertyValue();
        String cellVal = User.statusEnum.get(state); 
        return cellVal;
    }
    }

    2) Hibernate Annotations 

    具体Hibernate Annotation请参看 http://wiki.springside.org.cn/display/springside/Hibernate+Annotation

    这里有个 User, Role, Resource 这个多对多对多的问题。有几个地方需要注意的,User和Role是多对多关系,而且这里User和Role都是主控方,无论删除User还是Role,都需要把中间表User_Role中相应的项删除。所以在User和Role里,都需要列出JoinTable,joinColumns和inverseJoinColumns。三者关系如下:

    在User里

    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY)
    @JoinTable(name = "SS_USER_ROLE", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "ROLE_ID") })
    public Set getRoles() {
    return this.roles;
    }

    在Role里

    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY)  。@JoinTable(name = "SS_USER_ROLE", joinColumns = { @JoinColumn(name = "ROLE_ID") }, inverseJoinColumns = { @JoinColumn(name = "USER_ID") })
    public Set getUsers() {
    return this.users;
    }

    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY)
    @JoinTable(name = "SS_ROLE_RESC", joinColumns = { @JoinColumn(name = "ROLE_ID") }, inverseJoinColumns = { @JoinColumn(name = "RESC_ID") })
    public Set getRescs() {
    return rescs;
    }

    在Resource里   

    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY) 。
    @JoinTable(name = "SS_ROLE_RESC", joinColumns = { @JoinColumn(name = "RESC_ID") }, inverseJoinColumns = { @JoinColumn(name = "ROLE_ID") })
    public Set getRoles() {
    return roles;
    }