读书人

初学者-教你把Acegi应用到实际项目(9)

发布时间: 2012-10-26 10:30:59 作者: rapoo

菜鸟-教你把Acegi应用到实际项目(9)-实现FilterInvocationDefinition

????? 在实际应用中,开发者有时需要将Web资源授权信息(角色与授权资源之间的定义)存放在RDBMS中,以便更好的管理。事实上,我觉得一般的企业应用都应当如此,因为这样可以使角色和Web资源的管理更灵活,更自由。那么,我们应当如何实现这个需求呢?在接下来的内容当中,我们将一一解说。
????? 我们都知道,一般Web资源授权信息的配置类似如下代码:

<bean id="filterInvocationInterceptor"name="code">create table webresdb(id bigint not null primary key,url varchar(60) not null,roles varchar(100) not null);INSERT INTO WEBRESDB VALUES(1,'/secure/**','ROLE_SUPERVISOR')INSERT INTO WEBRESDB VALUES(2,'/authenticate/**','ROLE_USER,ROLE_SUPERVISOR')INSERT INTO WEBRESDB VALUES(3,'/**','IS_AUTHENTICATED_ANONYMOUSLY')

?

2、增加类似于EntryHolder的辅助类RdbmsEntryHolder
????? 通过分析PathBasedFilterInvocationDefinitionMap类,我们发现,对于每行Web? 资源授权需求,比如“/secure/**=ROLE_SUPERVISOR”,它会借助于内部类EntryHolder存储各行内容。EntryHolder暴露了antPath和configAttributeDefinition属性,前者存储类似“/secure/**”的内容,而后者会通过configAttributeDefinition对象存储类似“ROLE_USER,ROLE_SUPERVISOR”的内容。在ConfigAttributeDefinition对象内部,它将各个角色拆分开,并分别借助SecurityConfig对象存储它们,即ROLE_USER和ROLE_SUPERVISOR,从而保存在configAttributes私有变量中。
????? 在此,我们提供了类似于EntryHolder的辅助类:

public class RdbmsEntryHolder implements Serializable {private static final long serialVersionUID = 2317309106087370323L;// 保护的URL模式private String url;// 要求的角色集合private ConfigAttributeDefinition cad;public String getUrl() {return url;}public ConfigAttributeDefinition getCad() {return cad;}public void setUrl(String url) {this.url = url;}public void setCad(ConfigAttributeDefinition cad) {this.cad = cad;}}

?

3、增加从RDBMS装载所有的Web资源授权信息的类

?

public class RdbmsSecuredUrlDefinition extends MappingSqlQuery{protected static final Log logger = LogFactory.getLog(RdbmsSecuredUrlDefinition.class);    protected RdbmsSecuredUrlDefinition(DataSource ds) {        super(ds, Constants.ACEGI_RDBMS_SECURED_SQL);    logger.debug("进入到RdbmsInvocationDefinition构建器中.........");            compile();    }    /**     * convert each row of the ResultSet into an object of the result type.     */    protected Object mapRow(ResultSet rs, int rownum)        throws SQLException {    logger.debug("抽取webresdb中的记录.........");        RdbmsEntryHolder rsh = new RdbmsEntryHolder();    // 设置URL    rsh.setUrl(rs.getString(Constants.ACEGI_RDBMS_SECURED_URL).trim());            ConfigAttributeDefinition cad = new ConfigAttributeDefinition();                String rolesStr = rs.getString(Constants.ACEGI_RDBMS_SECURED_ROLES).trim();        // commaDelimitedListToStringArray:Convert a CSV list into an array of Strings        // 以逗号为分割符, 分割字符串        String[] tokens =         StringUtils.commaDelimitedListToStringArray(rolesStr); // 角色名数组        // 构造角色集合        for(int i = 0; i < tokens.length;++i)        cad.addConfigAttribute(new SecurityConfig(tokens[i]));                //设置角色集合        rsh.setCad(cad);            return rsh;    }}

?

????? 这样,我们可以利用RdbmsSecuredUrlDefinition将webresdb中的各行记录组装成RdbmsEntryHolder对象,进而返回RdbmsEntryHolder集合给调用者。

?

4、增加FilterInvocationDefinitionSource的实现类

?

/** * FilterInvocationDefinitionSource实现类1 *  * @author qiuzj *  */public class RdbmsFilterInvocationDefinitionSource extends JdbcDaoSupportimplements FilterInvocationDefinitionSource {protected static final Log logger = LogFactory.getLog(RdbmsFilterInvocationDefinitionSource.class);private RdbmsSecuredUrlDefinition rdbmsInvocationDefinition;private PathMatcher pathMatcher = new AntPathMatcher();private Ehcache webresdbCache;/** * 实现ObjectDefinitionSource接口的方法 * 最核心的方法, 几乎可以认为RdbmsFilterInvocationDefinitionSource的其他大部分方法都是为这一方法服务的 *      * Accesses the <code>ConfigAttributeDefinition</code> that applies to a given secure object.<P>Returns     * <code>null</code> if no <code>ConfigAttribiteDefinition</code> applies.</p>     *     * @param object the object being secured     *     * @return the <code>ConfigAttributeDefinition</code> that applies to the passed object     * @返回 适用于传入对象的ConfigAttributeDefinition(角色集合)     *     * @throws IllegalArgumentException if the passed object is not of a type supported by the     *         <code>ObjectDefinitionSource</code> implementation     */public ConfigAttributeDefinition getAttributes(Object object)throws IllegalArgumentException {if ((object == null) || !this.supports(object.getClass())) {throw new IllegalArgumentException("抱歉,目标对象不是FilterInvocation类型");}// 抽取出待请求的URLString url = ((FilterInvocation) object).getRequestUrl();logger.info("待请示的URL: " + url);// 获取所有RdbmsEntryHolder列表(url与角色集合对应列表)List list = this.getRdbmsEntryHolderList();if (list == null || list.size() == 0)return null;// 去掉待请求url参数信息int firstQuestionMarkIndex = url.indexOf("?");if (firstQuestionMarkIndex != -1) {url = url.substring(0, firstQuestionMarkIndex);}Iterator iter = list.iterator();// 循环判断用户是否有权限访问当前url, 有则返回ConfigAttributeDefinition(角色集合)while (iter.hasNext()) {RdbmsEntryHolder entryHolder = (RdbmsEntryHolder) iter.next();// 判断当前访问的url是否符合entryHolder.getUrl()模式, 即判断用户是否有权限访问当前url// 如url="/secure/index.jsp", entryHolder.getUrl()="/secure/**", 则有权限访问boolean matched = pathMatcher.match(entryHolder.getUrl(), url);if (logger.isDebugEnabled()) {logger.debug("匹配到如下URL: '" + url + ";模式为 "+ entryHolder.getUrl() + ";是否被匹配:" + matched);}// 如果在用户所有被授权的URL中能找到匹配的, 则返回该ConfigAttributeDefinition(角色集合)if (matched) {return entryHolder.getCad();}}return null;}/** * 实现接口方法 *      * If available, all of the <code>ConfigAttributeDefinition</code>s defined by the implementing class.<P>This     * is used by the {@link AbstractSecurityInterceptor} to perform startup time validation of each     * <code>ConfigAttribute</code> configured against it.</p>     *     * @return an iterator over all the <code>ConfigAttributeDefinition</code>s or <code>null</code> if unsupported     * @返回 ConfigAttributeDefinition迭代集合(Iterator)     */public Iterator getConfigAttributeDefinitions() {        Set set = new HashSet();        Iterator iter = this.getRdbmsEntryHolderList().iterator();        while (iter.hasNext()) {        RdbmsEntryHolder entryHolder = (RdbmsEntryHolder) iter.next();            set.add(entryHolder.getCad());        }        return set.iterator();}/** * 实现接口方法, 检验传入的安全对象是否是与FilterInvocation类相同类型, 或是它的子类 * getAttributes(Object object)方法会调用这个方法 * 保证String url = ((FilterInvocation) object).getRequestUrl();的正确性 *      * Indicates whether the <code>ObjectDefinitionSource</code> implementation is able to provide     * <code>ConfigAttributeDefinition</code>s for the indicated secure object type.     *     * @param clazz the class that is being queried     *     * @return true if the implementation can process the indicated class     */public boolean supports(Class clazz) {if (FilterInvocation.class.isAssignableFrom(clazz)) {return true;} else {return false;}}/** * 覆盖JdbcDaoSupport中的方法, 用于将数据源传入RdbmsSecuredUrlDefinition中 * JdbcDaoSupport实现了InitializingBean接口, 该接口中的afterPropertiesSet()方法 * 用于在所有spring bean属性设置完毕后做一些初始化操作, BeanFactory会负责调用它 * 而在JdbcDaoSupport的实现中, afterPropertiesSet()方法调用了initDao()方法, 故我们 * 借此做一些初始化操作.  * 在此用于将数据源传入RdbmsSecuredUrlDefinition中 */protected void initDao() throws Exception {logger.info("第一个执行的方法: initDao()");this.rdbmsInvocationDefinition = new RdbmsSecuredUrlDefinition(this.getDataSource()); // 传入数据源, 此数据源由Spring配置文件注入if (this.webresdbCache == null)throw new IllegalArgumentException("必须为RdbmsFilterInvocationDefinitionSource配置一EhCache缓存");}/** * 获取所有RdbmsEntryHolder列表(url与角色集合对应列表) *  * @return */private List getRdbmsEntryHolderList(){List list = null;Element element = this.webresdbCache.get("webres");if (element != null){ // 如果缓存中存在RdbmsEntryHolder列表, 则直接获取返回list = (List) element.getValue();} else { // 如果缓存中不存在RdbmsEntryHolder列表, 则重新查询, 并放到缓存中list = this.rdbmsInvocationDefinition.execute();Element elem = new Element("webres", list);this.webresdbCache.put(elem);}//list = this.rdbmsInvocationDefinition.execute();return list;}/** * 用于Spring注入 *  * @param webresdbCache */public void setWebresdbCache(Ehcache webresdbCache) {this.webresdbCache = webresdbCache;}}

?

????? 由于RdbmsFilterInvocationDefinitionSource是针对Web资源的,因此它实现的supports()方法需要评估FilterInvocation对象。另外,为了减少同RDBMS的交互次数,我们启用了Spring EhCache服务。

?

?5、通过Spring DI注入RdbmsFilterInvocationDefinitionSource

?

<bean id="filterInvocationInterceptor"/></bean><bean id="rdbmsFilterInvocationDefinitionSource"ref="dataSource" /><property name="webresdbCache" ref="webresCacheBackend" /></bean><bean id="webresCacheBackend"/></property><property name="cacheName"><value>webresdbCache</value></property></bean>

?

?

?6、运行说明
?下载源代码,我提供了相应脚本,存放在Acegi8WebRoot\rdbms目录下。
1)、在rdbms目录下运行server.bat文件,启动hsqldb数据库。关于hsqldb的使用说明,请参考“菜鸟-手把手教你把Acegi应用到实际项目中(6)” http://zhanjia.iteye.com/blog/258282
?2)、运行Acegi8项目
?3)、用户名为javaee、qiuzj,密码为password

????? 至此,我们此节所讲的内容已结束,大家可以下载源代码以便调试。另外,代码中还提供了另一个版本的实现类RdbmsFilterInvocationDefinitionSourceVersion2,它继承了AbstractFilterInvocationDefinitionSource,在一定程序上减少了代码量,朋友们可以自行研究。

7、其他说明
开发环境:
MyEclipse 5.0GA
Eclipse3.2.1
JDK1.5.0_10
tomcat5.5.23
acegi-security-1.0.7
Spring2.0


Jar包:
acegi-security-1.0.7.jar
Spring.jar(2.0.8)
commons-codec.jar
jstl.jar (1.1)
standard.jar
commons-logging.jar(1.0)
hsqldb.jar(1.8.0.10)
log4j-1.2.13.jar
ehcache-1.3.0.jar

?

?

?更正注释, 红色部分为更改后的注释:

// 循环判断是否对当前url设置了安全角色访问机制, 有则返回相应的ConfigAttributeDefinition(角色集合), 否则返回null

while (iter.hasNext()) {RdbmsEntryHolder entryHolder = (RdbmsEntryHolder) iter.next();boolean matched = pathMatcher.match(entryHolder.getUrl(), url);if (logger.isDebugEnabled()) {logger.debug("匹配到如下URL: '" + url + ";模式为 "+ entryHolder.getUrl() + ";是否被匹配:" + matched);}if (matched) {return entryHolder.getCad();}}

?

?

?

?



谢谢了!


<bean id="inMemDaoImpl" name="code"><bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"> …… <property name="objectDefinitionSource"> <value><![CDATA[ CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /secure/**=ROLE_SUPERVISOR /authenticate/**=ROLE_USER,ROLE_SUPERVISOR ]]></value> </property> </bean>

从第一篇开始看,一篇一篇的看就会明白了

读书人网 >软件架构设计

热点推荐