- 论坛徽章:
- 0
|
1. 问题提出
在使用 Acegi Security Framework 的过程中, 如果细心的话, 会发现其资源和角色配置是在配置文件中的, 下面是 Appfuse 中相关配置 :
以下是代码:
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/ signup.html = ROLE_ANONYMOUS,admin,tomcat
/ clickstreams.js
p = admin
[Ctrl+A 全部选择]
上面的配置从功能上实现了资源与角色的映射, 但用户可能会提出在运行期动态改变权限分配的需求, 配置文件策略可能略显不足, 下面我将提供一种基于数据库的策略解决此问题.
2. E-R 模型
下图是需要的 E-R 模型
![]()
图1 Acegi 标准 RBAC E-R设计
图中的用户与角色不再多做解释, 我们主要关注一下 Permission 表 和 Resource 表, 这里 Resource 表用于存储系统资源, 在 web 层一般来说就是 url, 如果使用 acl, 就是 aclClass, 此时 Permission 表中的 aclMask 用来存储对应的 acl 权限, 考虑到 acl 在 web 项目中使用率不高, 下面我将着重介绍 web 层的权限控制, 对 acl 有兴趣的读者可以自己参阅 Acegi Reference Guide.
3. 如何阻止 acegi 从配置文件读取权限配置
从 Appfuse 中的示例性配置可以看出, acegi 对权限配置的要求是 “ 资源 = 角色1, 角色2 … 角色 n ”, 看过源代码的读者应该知道, 最终这些配置将被组装为
net.sf.acegisecurity.intercept. ObjectDefinitionSource
web 层对应的实现是
net.sf.acegisecurity.intercept.web. FilterInvocationDefinitionSource
那么我们怎么才能用数据库的数据来组装 FilterInvocationDefinitionSource ? 这里涉及到一个 PropertyEditor 问题, 在 Acegi 中, FilterInvocationDefinitionSource 是通过
net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSourceEditor
组装的, 假如我们不想让 FilterInvocationDefinitionSourceEditor 从配置文件中读取权限配置, 就需要自己实现一个 ProdertyEditor 来覆盖默认实现, 下面是我的 配置 :
![]()
图2 customerEditorConfigurer 配置
那么, 这个 PropertyEditor 中需要做些什么呢 ? 要做的就是使用一个比较特殊的标记, 当遇到这个特殊标记的时候直接略过解析, 我这里使用的标记是 “DONT_USE_ME”, 然后在 PropertyEditor 中简单的如下实现即可:
以下是代码:
package com.skyon.um.security.acegi.intercept.web;
import java.beans.PropertyEditorSupport;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import net.sf.acegisecurity.ConfigAttributeDefinition;
import net.sf.acegisecurity.ConfigAttributeEditor;
import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionMap;
import net.sf.acegisecurity.intercept.web.PathBasedFilterInvocationDefinitionMap;
import net.sf.acegisecurity.intercept.web.RegExpBasedFilterInvocationDefinitionMap;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class FilterInvocationDefinitionSourceDynamicExtentionEditor extends PropertyEditorSupport {
public static final String ANT_PATH_KEY = " PATTERN_TYPE_APACHE_ANT " ;
public static final String LOWER_CASE_URL_KEY = " CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON " ;
public static final String DONT_USE_ME_KEY = " DONT_USE_ME " ;
public static final String STAND_DELIM_CHARACTER = " , " ;
private static final Log logger = LogFactory.getLog(FilterInvocationDefinitionSourceDynamicExtentionEditor. class );
/** */ /**
* @see java.beans.PropertyEditorSupport#setAsText(java.lang.String)
*/
public void setAsText(String text) throws IllegalArgumentException {
FilterInvocationDefinitionMap source = new RegExpBasedFilterInvocationDefinitionMap();
if (StringUtils.isBlank(text)) {
// Leave target object empty
} else {
// Check if we need to override the default definition map
if (text.lastIndexOf(ANT_PATH_KEY) != - 1 ) {
source = new PathBasedFilterInvocationDefinitionMap();
if (logger.isDebugEnabled()) {
logger.debug(( " Detected PATTERN_TYPE_APACHE_ANT directive; using Apache Ant style path expressions " ));
}
}
if (text.lastIndexOf(LOWER_CASE_URL_KEY) != - 1 ) {
if (logger.isDebugEnabled()) {
logger.debug( " Instructing mapper to convert URLs to lowercase before comparison " );
}
source.setConvertUrlToLowercaseBeforeComparison( true );
}
if (text.indexOf(DONT_USE_ME_KEY) != - 1 ) {
if (logger.isDebugEnabled()) {
logger.debug( " DETECTED " + DONT_USE_ME_KEY + " directive; skip parse, Use " + EhCacheBasedFilterInvocationDefinitionSourceCache. class + " to parse! " );
}
addSecureUrl(source, " /dontuseme " , " dontuseme " );
} else {
BufferedReader br = new BufferedReader( new StringReader(text));
int counter = 0 ;
String line;
while ( true ) {
counter ++ ;
try {
line = br.readLine();
} catch (IOException ioe) {
throw new IllegalArgumentException(ioe.getMessage());
}
if (line == null ) {
break ;
}
line = line.trim();
if (logger.isDebugEnabled()) {
logger.debug( " Line " + counter + " : " + line);
}
if (line.startsWith( " // " )) {
continue ;
}
if (line.equals(LOWER_CASE_URL_KEY)) {
continue ;
}
if (line.lastIndexOf( ' = ' ) == - 1 ) {
continue ;
}
// Tokenize the line into its name/value tokens
String[] nameValue = org.springframework.util.StringUtils.delimitedListToStringArray(line, " = " );
String name = nameValue[ 0 ];
String value = nameValue[ 1 ];
addSecureUrl(source, name, value);
}
}
}
setValue(source);
}
/** */ /**
* @param source
* @param name
* @param value
* @throws IllegalArgumentException
*/
private void addSecureUrl(FilterInvocationDefinitionMap source, String name, String value) throws IllegalArgumentException {
// Convert value to series of security configuration attributes
ConfigAttributeEditor configAttribEd = new ConfigAttributeEditor();
configAttribEd.setAsText(value);
ConfigAttributeDefinition attr = (ConfigAttributeDefinition) configAttribEd.getValue();
// Register the regular expression and its attribute
source.addSecureUrl(name, attr);
}
}
[Ctrl+A 全部选择]
Ok, 现在 FilterInvocationDefinitionSourceDynamicExtentionEditor 遇到配置文件中的 “DONT_USE_ME” 时将直接略过, 下面是我的 filterInvocationInterceptor 配置:
![]()
图3 filterInvocationInterceptor 配置
现在, 我们已经成功阻止 acegi 从配置文件读取权限配置, 下一个问题就是:
4. 如何从表中数据组装 FilterInvocationDefinitionSource
为了实现此功能, 需要一个自定义的资源定义接口来提供 FilterInvocationDefinitionSource, 此接口可能会是这样 :
以下是代码:
package com.skyon.um.security.acegi.intercept.web;
import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
import org.springframework.beans.factory.FactoryBean;
import com.skyon.framework.spring.ehcache.FlushableCache;
public interface FilterInvocationDefinitionSourceCache extends FactoryBean, FlushableCache {
/** */ /** The Perl5 expression */
int REOURCE_EXPRESSION_PERL5_REG_EXP = 1 ;
/** */ /** The ant path expression */
int RESOURCE_EXPRESSION_ANT_PATH_KEY = 2 ;
void setResourceExpression( int resourceExpression);
void setConvertUrlToLowercaseBeforeComparison( boolean convertUrlToLowercaseBeforeComparison);
FilterInvocationDefinitionSource getFilterInvocationDefinitionSource();
}
[Ctrl+A 全部选择]
其核心方法是 FilterInvocationDefinitionSource getFilterInvocationDefinitionSource(), 此方法将代替配置文件提供资源和角色的配置, 下面是实现
以下是代码:
package com.skyon.um.security.acegi.intercept.web;
import net.sf.acegisecurity.ConfigAttributeDefinition;
import net.sf.acegisecurity.ConfigAttributeEditor;
import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionMap;
import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
import net.sf.acegisecurity.intercept.web.PathBasedFilterInvocationDefinitionMap;
import net.sf.acegisecurity.intercept.web.RegExpBasedFilterInvocationDefinitionMap;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import com.skyon.framework.spring.ehcache.CacheUtils;
import com.skyon.framework.spring.ehcache.SerializableObjectProvider;
import com.skyon.framework.spring.support.MandatorySingletonBeanSupport;
public class EhCacheBasedFilterInvocationDefinitionSourceCache extends MandatorySingletonBeanSupport
implements FilterInvocationDefinitionSourceCache, InitializingBean {
private static final Log logger = LogFactory.getLog(EhCacheBasedFilterInvocationDefinitionSourceCache. class );
private int resourceExpression;
private boolean convertUrlToLowercaseBeforeComparison = false ;
private ResourceMappingProvider resourceMappingProvider;
private Cache cache;
private Object lock = new Object();
public FilterInvocationDefinitionSource getFilterInvocationDefinitionSource() {
synchronized (lock) {
Element element = CacheUtils.get(getCache(), " key " );
if (element == null ) {
FilterInvocationDefinitionSource definitionSource = (FilterInvocationDefinitionSource) getFilterInvocationDefinitionSourceFromBackend();
element = new Element( " key " , new SerializableObjectProvider(definitionSource));
getCache().put(element);
}
return (FilterInvocationDefinitionSource) ((SerializableObjectProvider) element.getValue()).getSourceObject();
}
}
public void flushCache() {
CacheUtils.flushCache(getCache());
getFilterInvocationDefinitionSource();
}
private FilterInvocationDefinitionMap getFilterInvocationDefinitionSourceFromBackend() {
logger.info( " 开始加载系统资源权限数据到缓存 " );
FilterInvocationDefinitionMap definitionSource = null ;
switch (resourceExpression) {
case REOURCE_EXPRESSION_PERL5_REG_EXP : {
definitionSource = new RegExpBasedFilterInvocationDefinitionMap();
break ;
}
case RESOURCE_EXPRESSION_ANT_PATH_KEY : {
definitionSource = new PathBasedFilterInvocationDefinitionMap();
break ;
}
default : {
throwException();
}
}
definitionSource.setConvertUrlToLowercaseBeforeComparison(isConvertUrlToLowercaseBeforeComparison());
ResourceMapping[] mappings = getResourceMappingProvider().getResourceMappings();
if (mappings == null || mappings.length == 0 ) {
return definitionSource;
}
for ( int i = 0 ; i ;
String[] recipents = mapping.getRecipients();
if (recipents == null || recipents.length == 0 ) {
if (logger.isErrorEnabled()) {
logger.error( " Notice, the resource : " + mapping.getResourcePath() + " has no recipents, it will access by any one ! " );
}
continue ;
}
StringBuffer valueBuffer = new StringBuffer();
for ( int j = 0 ; j
[Ctrl+A 全部选择]
实现采用 EhCache 缓存资源权限配置, 这样如果资源权限数据发生变化, 可以 flush Cache 从数据库重新读取. 至于代码中的 ResourceMapingProvider 实现, 简单的把 Resource 表和 Role 表中的数据读取过来即可, 这里不再赘述.
5. 如何将数据库中的权限配置传递给 FilterInvocationInterceptor
完成以上步骤后, 最后一步就是如何把 FilterInvocationDefinitionSourceCache 中的 FilterInvocationDefinitionSource 传递给 FilterInvocationInterceptor, Simple implemention :
代码
public class SecurityEnforcementDynamicExtensionFilter extends
SecurityEnforcementFilter implements InitializingBean {
… 略去
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// get the defination source form soure holder
getFilterSecurityInterceptor().setObjectDefinitionSource(getDefinitionSourceCache().getFilterInvocationDefinitionSource());
}
}
配置:
It’s Over Now.
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/57965/showart_469453.html |
|