SpringSide 对Acegi的扩展应用实例

来源:百度文库 编辑:神马文学网 时间:2024/09/30 00:22:10
作者: cac,作者保留版权,转载请注明出处。
1. Acegi的不足之处
在上面Acegi关键组件详细描述的章节中讲述了FilterSecurityInterceptor和MethodSecurityInterceptor。这两个分别针对web和方法调用安全级别保护的Bean中,都需要在objectDefinitionSource属性里定义资源与权限的对应关系,也就是说这个对应关系必须写在XML里。





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




false





sample.contact.ContactManager.create=ROLE_USER
sample.contact.ContactManager.getAllRecipients=ROLE_ADMIN


当资源或权限改变的时候,就需要直接去更改源代码中的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对象组。



select name,passwd,status from ss_users



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 = ?



select res_string, res_type from ss_resources



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 = ?



3) 缓存对象,加快权限认证速度
如果每次都从数据库中读取资源信息再进行权限认证,则需要频繁访问数据库。使用缓存来保存权限认证信息是个比较好的解决方案。
AcegiCacheManagerFactoryBean是以Factory模式生成AcegiCacheManager实例。通过authenticationService获取UserDetails和ResourceDetails对象组,并放入userCache和resourceCache两个缓存中,由AcegiCacheManager来统一管理两个缓存。




4) 用户访问资源需进行认证时,从缓存中读取权限信息
从上面可以看到, Acegi提供的两个拦截器FilterSecurityInterceptor和MethodSecurityInterceptor都是从objectDefinitionSource中的Value读取出权限信息的。所以我们在这扩展了这两个拦截器,CacheBaseUrlDefinitionSource和CacheBaseMethodDefinitionSource都实现了ObjectDefinitionSource接口的getAttributes(object)方法,让它们能通过acegiCacheManager从缓存中读取权限信息。








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





    ......
    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;
    }