- 浏览: 240054 次
- 性别:
- 来自: 上海
最新评论
-
weigeshikebi:
不得不赞一个
解惑 spring 嵌套事务 -
siemens800:
Mac OS X 10.7.2 的光盘还有挖,帅锅帮刻个盘发来 ...
MacBook 升级内存记 -
cry615:
帖子很不错,java里任何一个东西都是一门学问,很有很强的逻辑 ...
理解 Java 的 GC 与 幽灵引用 -
sharkka:
sogo1986 写道楼主举的例 ...
解惑 spring 嵌套事务 -
sogo1986:
楼主举的例子并没用体 ...
解惑 spring 嵌套事务
本文原出处 : http://starcraft.blogdriver.com/starcraft/1089862.html
Acegi 资源配置动态扩展实现
王 政 (Feiing) 于 2005-12-11
1. 问题提出
在使用 Acegi Security Framework 的过程中, 如果细心的话, 会发现其资源和角色配置是在配置文件中的, 下面是 Appfuse 中相关配置 :
上面的配置从功能上实现了资源与角色的映射, 但用户可能会提出在运行期动态改变权限分配的需求, 配置文件策略可能略显不足, 下面我将提供一种基于数据库的策略解决此问题.
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 中简单的如下实现即可:
Ok, 现在 FilterInvocationDefinitionSourceDynamicExtentionEditor 遇到配置文件中的 “DONT_USE_ME” 时将直接略过, 下面是我的 filterInvocationInterceptor 配置:
图3 filterInvocationInterceptor 配置
现在, 我们已经成功阻止 acegi 从配置文件读取权限配置, 下一个问题就是:
4. 如何从表中数据组装 FilterInvocationDefinitionSource
为了实现此功能, 需要一个自定义的资源定义接口来提供 FilterInvocationDefinitionSource, 此接口可能会是这样 :
其核心方法是 FilterInvocationDefinitionSource getFilterInvocationDefinitionSource(), 此方法将代替配置文件提供资源和角色的配置, 下面是实现
实现采用 EhCache 缓存资源权限配置, 这样如果资源权限数据发生变化, 可以 flush Cache 从数据库重新读取. 至于代码中的 ResourceMapingProvider 实现, 简单的把 Resource 表和 Role 表中的数据读取过来即可, 这里不再赘述.
5. 如何将数据库中的权限配置传递给 FilterInvocationInterceptor
完成以上步骤后, 最后一步就是如何把 FilterInvocationDefinitionSourceCache 中的 FilterInvocationDefinitionSource 传递给 FilterInvocationInterceptor, Simple implemention :
配置:
It’s Over Now.
本文引用代码及配置文件参见附件.[/url]
实现采用 EhCache 缓存资源权限配置, 这样如果资源权限数据发生变化, 可以 flush Cache 从数据库重新读取.
这里我想问一点,这种flush Cache的策略是EhCache自动触发的,还是程序中判断的?
如果是自动触发的,需要对EhCache作什么特殊配置么?
目前我这里有 4 种策略:
1. 当资源权限数据在本系统内维护时, 最好的办法是采用 Observer 模式, DAO 修改数据后 PublishEvent, Listener 接受到信息 flushCache, 这也是最理想的情况
当资源权限数据在其他系统维护时 (我目前的环境), 大致有以下 3 种策略:
2. WebService 做通讯 flushCache (推荐)
3. 使用 EHCache 自身的定时刷新功能 flushCache (比较消耗资源)
4. 用户修改资源权限配置后, 在 ui 上人工 flushCache
我目前采用的是 4, 无奈之举
实现采用 EhCache 缓存资源权限配置, 这样如果资源权限数据发生变化, 可以 flush Cache 从数据库重新读取.
这里我想问一点,这种flush Cache的策略是EhCache自动触发的,还是程序中判断的?
如果是自动触发的,需要对EhCache作什么特殊配置么?
ContextHolder 已经做得这么好了, 可以让你随时随地的取, 还能怎么做?
谢谢提示,关键是我对楼主说的 权限块和业务块的接口RemoteUserContextUtils 的具体作用和使用场合不是很清楚。我还没有真正实施在业务逻辑中,所以想请楼主先指点一下,就说说这个RemoteUserContextUtils 的使用方法吧。
谢谢~~~~
标准的 Web 权限中, 无非就是 HttpServletRequest#getRemoteUser() 方法和 HttpServletRequest#isUserInRole(String role) 方法, 通过自定义的 RemoteUserContextUtils 实现上述两个方法, 带来的好处是解除了权限和 Web 层的耦合, 这就是 Acegi 上下文带来的好处. 至于业务逻辑, 只需要调用上述两个方法即可, 至于某角色是否有某资源的访问权, 已经定义在 Permission 表中了, 运行期 AccessDecisionManager 会自动拦截非法访问, 业务代码完全不用关心权限问题
呵呵, 这是一个隐藏规则, 但是偶也不喜欢这种规则, 有种暗箱操作的感觉
ContextHolder 已经做得这么好了, 可以让你随时随地的取, 还能怎么做?
谢谢提示,关键是我对楼主说的 权限块和业务块的接口RemoteUserContextUtils 的具体作用和使用场合不是很清楚。我还没有真正实施在业务逻辑中,所以想请楼主先指点一下,就说说这个RemoteUserContextUtils 的使用方法吧。
谢谢~~~~
ContextHolder 已经做得这么好了, 可以让你随时随地的取, 还能怎么做?
这个难度增大只限于实现本身, 对客户端调用完全透明, 我们用 AOP 做权限控制就是为了把控制逻辑完全从 business 中分离, 在我的项目中, 权限模块与业务模块的接口只有一个, 除了这个 RemoteUserContextUtils , 业务模块完全不需要知道使用了 Acegi, 要做的只是分配好 Role 和 Resource 即可
呵呵, 我感觉这已经是最少的代码量了, 有更便捷的实现吗?
Acegi 资源配置动态扩展实现
王 政 (Feiing) 于 2005-12-11
1. 问题提出
在使用 Acegi Security Framework 的过程中, 如果细心的话, 会发现其资源和角色配置是在配置文件中的, 下面是 Appfuse 中相关配置 :
<bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"> <property name="authenticationManager"><ref local="authenticationManager"/></property> <property name="accessDecisionManager"><ref local="accessDecisionManager"/></property> <property name="objectDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /signup.html=ROLE_ANONYMOUS,admin,tomcat /clickstreams.jsp=admin </value> </property> </bean>
上面的配置从功能上实现了资源与角色的映射, 但用户可能会提出在运行期动态改变权限分配的需求, 配置文件策略可能略显不足, 下面我将提供一种基于数据库的策略解决此问题.
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 中简单的如下实现即可:
/* * Copyright 2004-2005 wangz. * Project shufe_newsroom */ 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; /** * @since 2005-8-4 * @author 王政 * @version $Id: FilterInvocationDefinitionSourceDynamicExtentionEditor.java,v 1.2 2005/11/04 15:55:07 wangzheng Exp $ */ 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); } }
Ok, 现在 FilterInvocationDefinitionSourceDynamicExtentionEditor 遇到配置文件中的 “DONT_USE_ME” 时将直接略过, 下面是我的 filterInvocationInterceptor 配置:
图3 filterInvocationInterceptor 配置
现在, 我们已经成功阻止 acegi 从配置文件读取权限配置, 下一个问题就是:
4. 如何从表中数据组装 FilterInvocationDefinitionSource
为了实现此功能, 需要一个自定义的资源定义接口来提供 FilterInvocationDefinitionSource, 此接口可能会是这样 :
/* * Copyright 2005-2010 the original author or autors * * http://www.skyon.com.cn * * Project { SkyonFramwork } */ 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; /** * <class>FilterInvocationDefinitionSourceCache</class> use to hold the global FilterInvocationDefinitionSource, * it keeps a static variable , if the source been changed(generally the database data), the reload method should be called * * @see com.skyon.um.security.acegi.intercept.event.FilterInvocationDefinitionSourceChangedEvent * @see com.skyon.um.security.acegi.intercept.event.FilterInvocationDefinitionSourceListener * @see com.skyon.um.security.acegi.intercept.web.SecurityEnforcementDynamicExtensionFilter * @since 2005-8-7 * @author 王政 * @version $Id: FilterInvocationDefinitionSourceCache.java,v 1.1 2005/11/04 15:55:07 wangzheng Exp $ */ 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; /** * Set resource expression, the value must be {@link #REOURCE_EXPRESSION_PERL5_REG_EXP} or {@link #RESOURCE_EXPRESSION_ANT_PATH_KEY} * @see #REOURCE_EXPRESSION_PERL5_REG_EXP * @see #RESOURCE_EXPRESSION_ANT_PATH_KEY * @param resourceExpression the resource expression */ void setResourceExpression(int resourceExpression); /** * Set whether convert url to lowercase before comparison * @param convertUrlToLowercaseBeforeComparison whether convertUrlToLowercaseBeforeComparison */ void setConvertUrlToLowercaseBeforeComparison(boolean convertUrlToLowercaseBeforeComparison); /** * Get the defination source, generally from a database schema * @return the defination source */ FilterInvocationDefinitionSource getFilterInvocationDefinitionSource(); }
其核心方法是 FilterInvocationDefinitionSource getFilterInvocationDefinitionSource(), 此方法将代替配置文件提供资源和角色的配置, 下面是实现
/* * Copyright 2005-2010 the original author or autors * * http://www.skyon.com.cn * * Project { SkyonFramwork } */ 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; /** * @since 2005-8-7 * @author 王政 * @version $Id: EhCacheBasedFilterInvocationDefinitionSourceCache.java,v 1.2 2005/11/17 09:38:25 wangzheng Exp $ */ 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(); /** * @see com.skyon.um.security.acegi.intercept.web.FilterInvocationDefinitionSourceCache#getFilterInvocationDefinitionSource() */ 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 < mappings.length; i++) { ResourceMapping mapping = mappings[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 < recipents.length; j++) { valueBuffer.append(recipents[j]); if (j < recipents.length - 1) { valueBuffer.append(FilterInvocationDefinitionSourceDynamicExtentionEditor.STAND_DELIM_CHARACTER); } } String value = valueBuffer.toString(); addSecureUrl(definitionSource, mapping.getResourcePath(), value); } logger.info(" 成功加载系统资源权限数据到缓存 ! "); return definitionSource; } /** * @param source * @param name * @param value * @throws IllegalArgumentException */ private synchronized 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); } public void afterPropertiesSet() throws Exception { if (resourceExpression != REOURCE_EXPRESSION_PERL5_REG_EXP && resourceExpression != RESOURCE_EXPRESSION_ANT_PATH_KEY) { throwException(); } Assert.notNull(getResourceMappingProvider(), " resourceMappingProvider must be specified"); Assert.notNull(getCache(), " cache must be specified"); } /** * @throws IllegalArgumentException */ private void throwException() throws IllegalArgumentException { throw new IllegalArgumentException("wrong resourceExpression value"); } /** * @return Returns the resourceMappingProvider. */ public ResourceMappingProvider getResourceMappingProvider() { return resourceMappingProvider; } /** * @param resourceMappingProvider The resourceMappingProvider to set. */ public void setResourceMappingProvider(ResourceMappingProvider resourceMappingProvider) { this.resourceMappingProvider = resourceMappingProvider; } /** * @return Returns the convertUrlToLowercaseBeforeComparison. */ public boolean isConvertUrlToLowercaseBeforeComparison() { return convertUrlToLowercaseBeforeComparison; } /** * @param convertUrlToLowercaseBeforeComparison The convertUrlToLowercaseBeforeComparison to set. */ public void setConvertUrlToLowercaseBeforeComparison( boolean convertUrlToLowercaseBeforeComparison) { this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison; } /** * @return Returns the resourceExpression. */ public int getResourceExpression() { return resourceExpression; } /** * @param resourceExpression The resourceExpression to set. */ public void setResourceExpression(int resourceExpression) { this.resourceExpression = resourceExpression; } /** * @return Returns the cache. */ public Cache getCache() { return cache; } /** * @param cache The cache to set. */ public void setCache(Cache cache) { this.cache = cache; } }
实现采用 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.
本文引用代码及配置文件参见附件.[/url]
评论
16 楼
Feiing
2005-12-21
非 Web 层的实现 MethodSecurityInterceptor, 请参考 http://forum.springframework.org/showthread.php?t=10185 , 思路基本相同
15 楼
差沙
2005-12-18
采用4应该是比较专业的做法了,因为这这些安全操作基本上都是技术人员来更改的,提供一个是否立即生效比较好,也比较人性。
现在正研究acegi的ACL,感觉acegi在这方面的设计还是不错的,值得看看。
现在正研究acegi的ACL,感觉acegi在这方面的设计还是不错的,值得看看。
14 楼
Feiing
2005-12-16
flyromza 写道
Feiing 写道
实现采用 EhCache 缓存资源权限配置, 这样如果资源权限数据发生变化, 可以 flush Cache 从数据库重新读取.
这里我想问一点,这种flush Cache的策略是EhCache自动触发的,还是程序中判断的?
如果是自动触发的,需要对EhCache作什么特殊配置么?
目前我这里有 4 种策略:
1. 当资源权限数据在本系统内维护时, 最好的办法是采用 Observer 模式, DAO 修改数据后 PublishEvent, Listener 接受到信息 flushCache, 这也是最理想的情况
当资源权限数据在其他系统维护时 (我目前的环境), 大致有以下 3 种策略:
2. WebService 做通讯 flushCache (推荐)
3. 使用 EHCache 自身的定时刷新功能 flushCache (比较消耗资源)
4. 用户修改资源权限配置后, 在 ui 上人工 flushCache
我目前采用的是 4, 无奈之举
13 楼
flyromza
2005-12-16
Feiing 写道
实现采用 EhCache 缓存资源权限配置, 这样如果资源权限数据发生变化, 可以 flush Cache 从数据库重新读取.
这里我想问一点,这种flush Cache的策略是EhCache自动触发的,还是程序中判断的?
如果是自动触发的,需要对EhCache作什么特殊配置么?
12 楼
Feiing
2005-12-15
差沙 写道
Feiing 写道
差沙 写道
弱弱的问一下,acegi没有给业务对象提供userDetailUtil之类的东西么?要是这样的话,acegi真的有必要加上这块。
ContextHolder 已经做得这么好了, 可以让你随时随地的取, 还能怎么做?
谢谢提示,关键是我对楼主说的 权限块和业务块的接口RemoteUserContextUtils 的具体作用和使用场合不是很清楚。我还没有真正实施在业务逻辑中,所以想请楼主先指点一下,就说说这个RemoteUserContextUtils 的使用方法吧。
谢谢~~~~
标准的 Web 权限中, 无非就是 HttpServletRequest#getRemoteUser() 方法和 HttpServletRequest#isUserInRole(String role) 方法, 通过自定义的 RemoteUserContextUtils 实现上述两个方法, 带来的好处是解除了权限和 Web 层的耦合, 这就是 Acegi 上下文带来的好处. 至于业务逻辑, 只需要调用上述两个方法即可, 至于某角色是否有某资源的访问权, 已经定义在 Permission 表中了, 运行期 AccessDecisionManager 会自动拦截非法访问, 业务代码完全不用关心权限问题
11 楼
Feiing
2005-12-15
差沙 写道
噢,我自己找到了,不好意思问这个问题。
一个class的PropertyEditor如果被恰当地命名并且放在与它提供支持的class同一个包内,那么Java标准的JavaBeans PropertyEditor查找机制就会自动地查找到这个PropertyEditor。
一个class的PropertyEditor如果被恰当地命名并且放在与它提供支持的class同一个包内,那么Java标准的JavaBeans PropertyEditor查找机制就会自动地查找到这个PropertyEditor。
呵呵, 这是一个隐藏规则, 但是偶也不喜欢这种规则, 有种暗箱操作的感觉
10 楼
差沙
2005-12-15
Feiing 写道
差沙 写道
弱弱的问一下,acegi没有给业务对象提供userDetailUtil之类的东西么?要是这样的话,acegi真的有必要加上这块。
ContextHolder 已经做得这么好了, 可以让你随时随地的取, 还能怎么做?
谢谢提示,关键是我对楼主说的 权限块和业务块的接口RemoteUserContextUtils 的具体作用和使用场合不是很清楚。我还没有真正实施在业务逻辑中,所以想请楼主先指点一下,就说说这个RemoteUserContextUtils 的使用方法吧。
谢谢~~~~
9 楼
差沙
2005-12-15
噢,我自己找到了,不好意思问这个问题。
一个class的PropertyEditor如果被恰当地命名并且放在与它提供支持的class同一个包内,那么Java标准的JavaBeans PropertyEditor查找机制就会自动地查找到这个PropertyEditor。
一个class的PropertyEditor如果被恰当地命名并且放在与它提供支持的class同一个包内,那么Java标准的JavaBeans PropertyEditor查找机制就会自动地查找到这个PropertyEditor。
8 楼
差沙
2005-12-15
在 Acegi 中, FilterInvocationDefinitionSource 是通过 net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSourceEditor 组装的
acegi是什么时候在哪配置的让FilterInvocationDefinitionSourceEditor 来组装的呢?
acegi是什么时候在哪配置的让FilterInvocationDefinitionSourceEditor 来组装的呢?
7 楼
Feiing
2005-12-15
差沙 写道
弱弱的问一下,acegi没有给业务对象提供userDetailUtil之类的东西么?要是这样的话,acegi真的有必要加上这块。
ContextHolder 已经做得这么好了, 可以让你随时随地的取, 还能怎么做?
6 楼
差沙
2005-12-15
弱弱的问一下,acegi没有给业务对象提供userDetailUtil之类的东西么?要是这样的话,acegi真的有必要加上这块。
5 楼
Feiing
2005-12-14
Jstar 写道
Thanks feiing.............
如果是这样.
用acegi会不会反而把开发难度增大了呢~~~~~~~
如果是这样.
用acegi会不会反而把开发难度增大了呢~~~~~~~
这个难度增大只限于实现本身, 对客户端调用完全透明, 我们用 AOP 做权限控制就是为了把控制逻辑完全从 business 中分离, 在我的项目中, 权限模块与业务模块的接口只有一个, 除了这个 RemoteUserContextUtils , 业务模块完全不需要知道使用了 Acegi, 要做的只是分配好 Role 和 Resource 即可
/* * Copyright 2005-2010 the original author or authors. * * http://www.skyon.com.cn * * Project cardAssistant */ package com.skyon.cams.security.acegi; import java.util.Iterator; import java.util.List; import net.sf.acegisecurity.AccessDecisionManager; import net.sf.acegisecurity.Authentication; import net.sf.acegisecurity.GrantedAuthority; import net.sf.acegisecurity.GrantedAuthorityImpl; import net.sf.acegisecurity.context.SecurityContext; import net.sf.acegisecurity.context.SecurityContextHolder; import net.sf.acegisecurity.providers.cas.CasAuthenticationToken; import net.sf.acegisecurity.providers.dao.UsernameNotFoundException; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import com.skyon.cams.entity.Organization; import com.skyon.cams.entity.User; import com.skyon.cams.security.acegi.providers.dao.hibernate.AuthenticationDaoHibernate; import com.skyon.cams.security.um.OrganizationRelationship; import com.skyon.cams.security.um.manager.OrganizationManager; import com.skyon.framework.core.WhenCrackException; /** * @since 2005-9-7 * @author 王政 * @version $Id: RemoteUserContextUtils.java,v 1.11 2005/12/01 09:19:59 wangzheng Exp $ */ public abstract class RemoteUserContextUtils { private static final Log logger = LogFactory.getLog(RemoteUserContextUtils.class);; /** * 得到当前登录用户 * @return 当前登录用户 */ public static UserDetails getRemoteUser(); { SecurityContext securityContext = SecurityContextHolder.getContext();; Authentication authentication = securityContext.getAuthentication();; Object principal = authentication.getPrincipal();; if (principal == null); { throw new UsernameNotFoundException(" The user doesn't exists ! ");; } if (UserDetails.class.isInstance(principal);); { // use Local Authentication return (UserDetails); principal; } else if (principal.equals(UserDetails.ANONYMOUS_USERNAME););{ return getAnonymousUser();; } else if (CasAuthenticationToken.class.isInstance(authentication);); { // use Yale Central Authentication Service (CAS); Single Sign On net.sf.acegisecurity.UserDetails userDetails = ((CasAuthenticationToken); authentication);.getUserDetails();; Assert.isInstanceOf(UserDetails.class, userDetails, getErrorMessage(userDetails.getClass();););; return (UserDetails); userDetails; } String errorMessage = getErrorMessage(principal.getClass(););; if (logger.isInfoEnabled();); { logger.info(errorMessage);; } throw new IllegalStateException(errorMessage);; } /** * @param detailClass * @return */ private static String getErrorMessage(Class detailClass); { StringBuffer sb = new StringBuffer();; sb.append(" UserDetails Should be ");; sb.append(UserDetails.class);; sb.append(" , but it is ");; sb.append(detailClass);; sb.append(" now, please make sure the custom dao ");; sb.append(AuthenticationDaoHibernate.class);; sb.append(" has been used. ");; return sb.toString();; } /** * Returns a boolean indicating whether the authenticated user is included * in the specified logical "role". Roles and role membership can be * defined using deployment descriptors. If the user has not been * authenticated, the method returns <code>false</code>. * * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String); * @param role a <code>String</code> specifying the name of the role * @return a <code>boolean</code> indicating whether * the remote user belongs to a given role; * <code>false</code> if the user has not been * authenticated */ public static boolean isUserInRole(String role); { UserDetails userDetails = getRemoteUser();; GrantedAuthority[] authorities = userDetails.getAuthorities();; if (authorities == null); { throw new UsernameNotFoundException("User has no GrantedAuthority");; } for (int i = 0; i < authorities.length; i++); { if (ObjectUtils.nullSafeEquals(authorities[i], role);); { return true; } } return false; } }
4 楼
Jstar
2005-12-14
Thanks feiing.............
如果是这样.
用acegi会不会反而把开发难度增大了呢~~~~~~~
如果是这样.
用acegi会不会反而把开发难度增大了呢~~~~~~~
3 楼
Feiing
2005-12-13
firebody 写道
only扩展一个数据库的实现,也要这么多代码? 晕倒。
呵呵, 我感觉这已经是最少的代码量了, 有更便捷的实现吗?
2 楼
firebody
2005-12-13
only扩展一个数据库的实现,也要这么多代码? 晕倒。
1 楼
Feiing
2005-12-13
貌似一次只能传 3 个附件?
发表评论
-
Bookmarks
2010-07-07 16:42 1526Architecture J2EE cluster htt ... -
XML validation error on request: cvc-complex-type
2010-04-15 21:45 1375see http://72.5.124.102/threa ... -
Spring LoadTimeWeaver 的那些事儿
2009-10-05 00:39 4079DDD 现在越来越流行了, 不管正确与否, new U ... -
理解 Java 的 GC 与 幽灵引用
2009-06-04 03:02 3286理解 Java 的 GC 与 幽灵引用 J ... -
基于 Apache Mina 的 RPC 实现 (长连接 webservice)
2008-11-27 23:26 4390写了一个基于 Apache Mina 和 SpringRemo ... -
Atomikos JTA for Hibernate3
2007-11-22 14:52 3364http://wiki.atomikos.org/bin/vi ... -
Spring AOP 概览与细节
2007-08-05 03:21 6031@王政 @2007-08-04 @转载请注明 ... -
解惑 spring 嵌套事务
2006-11-25 01:03 36205解惑 spring 嵌套事务 /** * @au ... -
使用 FactoryBean 让你的 spring 配置动起来
2006-11-01 17:43 10491看到不少朋友讨论 spring 配置时认为 spring 配置 ... -
一个可能的列级权限控制方案讨论
2006-05-25 18:45 18010最近的项目需要做到列级权限控制, 大意如下 publi ... -
Spring 事务简化配置
2006-03-21 00:33 36793在 spring 中, 事务管理一般是通过声明一个 txPr ... -
再论 Acegi 权限存储策略
2006-02-18 00:17 12864本文原出处 http://starcraft.blogdriv ... -
以前写的一篇介绍 Acegi 的文档
2006-01-05 09:51 13296半年前写的, 版本是 0.8.3, 主要是翻译了一些 ref ...
相关推荐
1. **源代码**:展示如何配置Acegi Security的XML文件,以及如何在Java代码中实现自定义的安全逻辑。 2. **测试类**:可能包含了用于测试不同安全场景的JUnit测试,这些测试可以帮助我们理解Acegi Security的各种...
### Acegi的详细配置实现 #### 一、整体架构概览 **Acegi Security** 是一个为Spring框架设计的安全管理工具,它提供了丰富的安全服务,包括认证(Authentication)、授权(Authorization)以及会话管理(Session ...
标题 "batis+acegi实现的动态权限控制" 暗示了这个项目是关于整合Spring框架中的Acegi安全模块和MyBatis ORM框架,来创建一个动态的权限管理系统。Acegi是Spring早期的安全组件,现在已被Spring Security所取代,但...
Acegi 安全框架是Spring生态...综上所述,这个资源对于理解和实施Acegi安全框架,特别是涉及动态数据库配置和权限控制的场景,具有很高的参考价值。开发者可以通过学习这些内容,提升自己在Spring安全方面的专业能力。
- "aopacegi"可能是一个包含Acegi与AOP相关配置或实现的文件,可能涉及切面的定义和安全策略的配置。 Acegi Security在过去的开发实践中扮演了重要角色,它的设计理念和实现方式对后来的安全框架产生了深远影响。...
由于Acegi默认配置文件策略可能无法满足所有场景下的需求,尤其是在需要动态调整用户权限的情况下。为了解决这一问题,可以采用基于数据库的策略来进行动态扩展。 - **基于数据库的策略**: 1. 将权限信息存储在...
4. **实现认证和授权逻辑**:根据应用需求,实现自定义的认证和授权类,这些类通常会扩展Acegi提供的基础类。 5. **测试和调试**:在实际运行环境中测试安全功能,确保用户权限设置正确,并使用Acegi提供的日志和...
总之,AceGI作为一个强大的安全框架,通过灵活的配置和扩展机制,使开发者能轻松地在Web应用中实现复杂的安全控制。它不仅支持基本的认证和授权,还允许自定义逻辑以适应各种业务场景。通过理解和熟练运用AceGI,...
本主题将深入探讨如何使用Acegi安全框架和Hibernate ORM工具动态实现基于角色的权限管理。Acegi(现已被Spring Security替代)是一个用于Java平台的安全框架,而Hibernate则是一个流行的关系型数据库持久化解决方案...
开发者可以在不改变业务逻辑的情况下,通过配置实现复杂的权限管理。尽管现在Spring Security已经成为更推荐的安全解决方案,但Acegi在早期对Spring应用的安全支持起到了重要作用,其设计理念和实现方式在Spring ...
这篇博客将深入解析一个配置了Acegi Security的`applicationContext-acegi-security.xml`文件,帮助我们理解如何将LDAP与Acegi集成以实现更安全的Web应用。 **LDAP基础** LDAP是一种标准的网络协议,用于存储和...
Acegi的主要目标是实现业务对象方法级别的安全控制,确保URL资源、业务类方法以及领域对象的访问得到适当限制。 1. URL资源的访问控制:Acegi能够设定不同用户群体对网页资源的访问权限。例如,所有用户可以访问...
Acegi还支持动态权限评估,允许在运行时根据特定条件决定用户是否有权访问资源。 接下来,我们讨论如何在Spring框架下实现这个系统。Spring的依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented...
用户无需编写大量代码,只需通过配置即可实现应用程序的安全管理。Acegi的主要优点在于其灵活性和可扩展性,能够方便地集成到Spring应用中,为用户提供了细粒度的权限控制。 在配置Acegi时,首先需要在`web.xml`...
3. **安全拦截**:Acegi利用Spring的AOP框架来实现对方法调用和URL访问的拦截。你可以定义安全拦截规则,例如,只有特定角色的用户才能访问某个方法或页面。 4. **角色与权限**:在Acegi中,用户可以被分配多个角色...
在处理复杂的权限管理时,Acegi允许开发者将资源权限数据存储到数据库,而不是仅局限于配置文件中,这样可以实现更灵活的权限动态扩展。 在传统的Acegi配置中,资源和角色的关系通常是硬编码在XML配置文件内的,...
Spring Acegi是一个安全框架,它为Spring...虽然Spring Acegi已不再更新,但其核心思想和机制在Spring Security中得到了继承和扩展。理解这个简单实例有助于你更好地理解和应用更现代的安全框架,如Spring Security。
通过这个实例,开发者可以学习如何配置Acegi的安全策略,如何在Spring应用中设置LDAP连接,以及如何设计和实现基于角色的访问控制(RBAC)。这有助于构建更健壮、安全的企业级应用,尤其适合大型组织和制造业等需要...