|
锁定老贴子 主题:用 acegi 控制url权限
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
|---|---|
| 作者 | 正文 |
|
最后更新时间:2005-08-22
为公司的权限控制模块做的一个spike,尚未完毕。使用的是acegi v0.82版。
1。web.xml加入acegi的filter: [code:1]<filter> <filter-name>Acegi Security System for Spring HttpSession Integration Filter</filter-name> <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> <init-param> <param-name>targetClass</param-name> <param-value>net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter</param-value> </init-param> </filter> <filter> <filter-name>Acegi Authentication Processing Filter</filter-name> <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> <init-param> <param-name>targetClass</param-name> <param-value>net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter</param-value> </init-param> </filter> <filter> <filter-name>Acegi HTTP Request Security Filter</filter-name> <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> <init-param> <param-name>targetClass</param-name> <param-value>net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter</param-value> </init-param> </filter> <filter-mapping> <filter-name>Acegi Security System for Spring HttpSession Integration Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>Acegi Authentication Processing Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>Acegi HTTP Request Security Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> [/code:1] 2.applicationContext-security.xml配置 [code:1]<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd" > <beans> <bean id="jdbcDaoImpl" class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl"> <property name="dataSource"><ref bean="dataSource"/></property> </bean> <bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="authenticationDao"> <ref local="jdbcDaoImpl"/> </property> </bean> <bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> <ref bean="daoAuthenticationProvider"/> </list> </property> </bean> <bean id="authenticationProcessingFilter" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter"> <property name="authenticationManager"> <ref bean="authenticationManager"/> </property> <property name="authenticationFailureUrl"> <value>/login.jsp?error=1</value> </property> <property name="defaultTargetUrl"> <value>/</value> </property> <property name="filterProcessesUrl"> <value>/j_acegi_security_check</value> </property> </bean> <bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/> <!-- 投票策略: Acegi安全系统中,使用投票策略的AccessDecisionManager共有三个具体实现类: AffirmativeBased、 ConsensusBased和UnanimousBased。它们的投票策略是, AffirmativeBased类只需有一个投票赞成即可通过; ConsensusBased类需要大多数投票赞成即可通过; 而UnanimousBased类需要所有的投票赞成才能通过。 --> <bean id="accessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased"> <property name="allowIfAllAbstainDecisions"> <value>false</value> </property> <property name="decisionVoters"> <list> <ref local="roleVoter"/> </list> </property> </bean> <bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter"> <property name="filterSecurityInterceptor"> <ref bean="filterInvocationInterceptor"/> </property> <property name="authenticationEntryPoint"> <ref bean="authenticationEntryPoint"/> </property> </bean> <!--bean id="httpSessionIntegrationFilter" class="net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter"/--> <bean id="httpSessionIntegrationFilter" class="net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter"> <property name="context"> <value>net.sf.acegisecurity.context.SecurityContextImpl</value> </property> </bean> <bean id="authenticationEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"> <property name="loginFormUrl"> <value>/login.jsp</value> </property> </bean> <bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"> <property name="authenticationManager"> <ref bean="authenticationManager"/></property> <property name="accessDecisionManager"> <ref bean="accessDecisionManager"/></property> <property name="objectDefinitionSource"> <!--value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /roles/**=ROLE_SUPERVISOR /users/**=ROLE_12 </value--> <!-- 将角色的url权限配置移到数据库 --> <ref local="objectDefinitionSource"/> </property> </bean> <bean id="objectDefinitionSource" class="com.sunbor.demo.service.acegi.FilterInvocationDefinitionSourceImp"> <property name="urlsService"><ref bean="urlsService"/></property> </bean> </beans>[/code:1] 3。数据库schema(sql server 2000): [code:1]alter table acl_permission drop constraint FK32AAC8A4225BBDD9 alter table authorities drop constraint FK2B0F1321F70AE816 alter table url_permissions drop constraint FK63D169B4EF611297 drop table acl_object_identity drop table acl_permission drop table authorities drop table url_permissions drop table urls drop table users create table acl_object_identity ( id numeric(19,0) identity not null, object_identity numeric(19,0) null, parent_object varchar(32) null, acl_class varchar(32) null, primary key (id) ) create table acl_permission ( id numeric(19,0) identity not null, recipient varchar(32) null, mask int null, objectid numeric(19,0) null, primary key (id) ) create table authorities ( username varchar(32) not null, authority varchar(32) not null, primary key (username, authority) ) create table url_permissions ( permission_id numeric(19,0) identity not null, roles varchar(32) not null, url_id numeric(19,0) null, primary key (permission_id) ) create table urls ( url_id numeric(19,0) identity not null, name varchar(32) null, url varchar(250) null, primary key (url_id) ) create table users ( username varchar(32) not null, password varchar(32) null, name varchar(32) null, enabled tinyint null, primary key (username) ) alter table acl_permission add constraint FK32AAC8A4225BBDD9 foreign key (objectid) references acl_object_identity alter table authorities add constraint FK2B0F1321F70AE816 foreign key (username) references users alter table url_permissions add constraint FK63D169B4EF611297 foreign key (url_id) references urls[/code:1] 4。为了实现角色的url权限动态配置,将权限信息保存到数据库表url_permissions 中,然后在安全检查的时候,从数据库读取url的角色权限。下面是自定义FilterInvocationDefinitionSource的实现类: [code:1]package com.sunbor.demo.service.acegi; import java.util.List; import net.sf.acegisecurity.ConfigAttributeDefinition; import net.sf.acegisecurity.SecurityConfig; import net.sf.acegisecurity.intercept.web.PathBasedFilterInvocationDefinitionMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.sunbor.demo.model.Urlpermissions; import com.sunbor.demo.model.Urls; import com.sunbor.demo.service.IUrlsService; public class FilterInvocationDefinitionSourceImp extends PathBasedFilterInvocationDefinitionMap implements IFilterInvocationDefinitionSource { private static final Log logger = LogFactory.getLog(FilterInvocationDefinitionSourceImp.class); private IUrlsService urlsService; public FilterInvocationDefinitionSourceImp() { super(); } public ConfigAttributeDefinition lookupAttributes(String url) { logger.info("request url:"+url); return super.lookupAttributes(url); } private void init(){ ConfigAttributeDefinition config = new ConfigAttributeDefinition(); List urls = urlsService.listUrls(); logger.info("-------------------- urls size1:"+urls.size()); for(int i=0;i<urls.size();i++){ Urls url = (Urls)urls.get(i); logger.info("================ url:"+url.getUrl()+" and permission size:"+url.getUrlpermissions().size()); if(url.getUrlpermissions().isEmpty()) continue; config = new ConfigAttributeDefinition(); for(int j=0;j<url.getUrlpermissions().size();j++){ Urlpermissions urlpermission = (Urlpermissions)url.getUrlpermissions().get(j); logger.info("================ url:"+url.getUrl()+" permission:"+urlpermission.getRoles()); SecurityConfig sc = new SecurityConfig(urlpermission.getRoles()); config.addConfigAttribute(sc); } addSecureUrl(url.getUrl(),config); } } public void setUrlsService(IUrlsService urlsService) { this.urlsService = urlsService; init(); } }[/code:1] 接口: [code:1]public interface IFilterInvocationDefinitionSource extends FilterInvocationDefinitionSource{ public abstract void setUrlsService(IUrlsService urlsService); }[/code:1] 这里继承了PathBasedFilterInvocationDefinitionMap类。如果要使用url正则表达式,则应继承RegExpBasedFilterInvocationDefinitionMap类。 到此,acegi的url权限控制已经有效了,由于acegi根据角色定义的前面是否为“ROLE_”开头来判断是否角色,所以在表 authorities 输入测试数据的时候,字段authority 要输入以“ROLE_”开头的字符串,url_permissions表中的roles字段也是如此。 5。汉字支持 由于使用 Modelstry 生成代码,生成的 jsp 中有包含汉字的url参数,而acegi并不支持参数包含汉字的url。虽然可以修改 Modelstry的模板,但是还是决定修改acegi的代码,使其支持url中包含汉字的参数值。 1)修改 net.sf.acegisecurity.intercept.web.FilterInvocation.java, getRequestUrl方法,修改后如下: [code:1] public String getRequestUrl() { String pathInfo = getHttpRequest().getPathInfo(); String queryString = getHttpRequest().getQueryString(); //String queryString = getQueryString(request); if(queryString !=null && queryString.length()>0){ try { queryString = decode(queryString); //System.out.println(FilterInvocation queryString:+queryString); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } return getHttpRequest().getServletPath() + ((pathInfo == null) ? "" : pathInfo) + ((queryString == null) ? "" : ("?" + queryString)); }[/code:1] 这里使用了decode方法,将url中的unicode代码转换为中文。 [code:1] public static String decode(String s) throws Exception { StringBuffer sb = new StringBuffer(); for(int i=0; i<s.length(); i++) { char c = s.charAt(i); switch (c) { case '+': sb.append(' '); break; case '%': try { sb.append((char)Integer.parseInt( s.substring(i+1,i+3),16)); } catch (NumberFormatException e) { throw new IllegalArgumentException(); } i += 2; break; default: sb.append(c); break; } } String result = sb.toString(); byte[] inputBytes = result.getBytes("8859_1"); return new String(inputBytes,"UTF8"); }[/code:1] &这样将直接支持中文。 2) 不过如果访问一个未授权匿名用户访问的资源时,将会转到登陆界面登陆,在登陆后才根据授权转移到用户所访问的页面。 然而如果用户所访问的页面url中包含中文,还将会出现乱码。 &于是,如下法修改: net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter类, &将方法 [code:1] protected void sendStartAuthentication(FilterInvocation fi, AuthenticationException reason) throws ServletException, IOException { ... }[/code:1] 中调用上面所改的方法FilterInvocation.getRequestUrl部分 [code:1] String targetUrl = request.getScheme()+ "://" + request.getServerName() +((includePort) ? (":" + port) : "") + request.getContextPath() +fi.getRequestUrl();[/code:1] 修改为: [code:1]String targetUrl = request.getScheme() +"://" + request.getServerName() + ((includePort) ?(":" + port) : "") + request.getContextPath() +request.getServletPath() + ((request.getPathInfo() == null) ? "" :request.getPathInfo()) + ((request.getQueryString() == null) ? "" :("?" + request.getQueryString());[/code:1] 即在保存到session中的queryString编码不改变。 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
|
|
| 返回顶楼 | |
|
最后更新时间:2005-08-22
引用 由于acegi根据角色定义的前面是否为“ROLE_”开头来判断是否角色,所以在表 authorities 输入测试数据的时候,字段authority 要输入以“ROLE_”开头的字符串,url_permissions表中的roles字段也是如此。
可以去掉ROLE的 [code:1] <bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"> <property name="rolePrefix"><value/></property> </bean> [/code:1] |
|
| 返回顶楼 | |
|
最后更新时间:2005-08-22
你 FilterInvocationDefinitionSourceImp 中从数据库读资源只能实现一次读取, 如果权限数据改变的话就不能动态读取了, 看看我的实现:
[code:1] /* * Copyright 2005-2010 the original author or autors * * http://www.skyon.com.cn * * Project { SkyonFramwork } */ package com.skyon.framework.security.acegi.intercept.web; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; /** * @see com.skyon.framework.security.acegi.intercept.web.FilterInvocationDefinitionSourceHolder * @see com.skyon.framework.security.acegi.intercept.event.FilterInvocationDefinitionSourceChangedEvent * @see com.skyon.framework.security.acegi.intercept.event.FilterInvocationDefinitionSourceListener * @since 2005-8-7 * @author 王政 * @version $Id: SecurityEnforcementDynamicExtensionFilter.java,v 1.2 2005/08/17 03:00:45 Administrator Exp $ */ public class SecurityEnforcementDynamicExtensionFilter extends SecurityEnforcementFilter implements InitializingBean { private FilterInvocationDefinitionSourceHolder definitionSourceHolder; /** * @return Returns the definitionSourceHolder. */ public FilterInvocationDefinitionSourceHolder getDefinitionSourceHolder() { return definitionSourceHolder; } /** * @param definitionSourceHolder The definitionSourceHolder to set. */ public void setDefinitionSourceHolder( FilterInvocationDefinitionSourceHolder definitionSourceHolder) { this.definitionSourceHolder = definitionSourceHolder; } /** * @see net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter#afterPropertiesSet() */ public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); Assert.notNull(getDefinitionSourceHolder(), " definitionSourceHolder must be specified "); } /** * 从 {@link FilterInvocationDefinitionSourceHolder} 中读取 {@link net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSource} * @see net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) */ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // get the defination source form soure holder getFilterSecurityInterceptor().setObjectDefinitionSource(definitionSourceHolder.getFilterInvocationDefinitionSource()); super.doFilter(request, response, chain); } } [/code:1] FilterInvocationDefinitionSourceHolder: [code:1] /* * Copyright 2005-2010 the original author or autors * * http://www.skyon.com.cn * * Project { SkyonFramwork } */ package com.skyon.framework.security.acegi.intercept.web; import org.springframework.beans.factory.FactoryBean; import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSource; /** * <class>FilterInvocationDefinitionSourceHolder</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.framework.security.acegi.intercept.event.FilterInvocationDefinitionSourceChangedEvent * @see com.skyon.framework.security.acegi.intercept.event.FilterInvocationDefinitionSourceListener * @see com.skyon.framework.security.acegi.intercept.web.SecurityEnforcementDynamicExtensionFilter * @since 2005-8-7 * @author 王政 * @version $Id: FilterInvocationDefinitionSourceHolder.java,v 1.1 2005/08/09 01:47:28 Administrator Exp $ */ public interface FilterInvocationDefinitionSourceHolder extends FactoryBean { /** 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(); /** * reoald the defination source, if the database's data has been changed, id should be called * @see com.skyon.framework.security.acegi.intercept.event.FilterInvocationDefinitionSourceChangedEvent * @see com.skyon.framework.security.acegi.intercept.event.FilterInvocationDefinitionSourceListener#onApplicationEvent(ApplicationEvent) */ void reload(); } [/code:1] 如果权限数据发生变化, 使用 listener 重新 load 数据: [code:1] /* * Copyright 2005-2010 the original author or autors * * http://www.skyon.com.cn * * Project { SkyonFramwork } */ package com.skyon.framework.security.acegi.intercept.event; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.util.Assert; import com.skyon.framework.security.acegi.intercept.web.FilterInvocationDefinitionSourceHolder; /** * * @since 2005-8-7 * @author 王政 * @version $Id: FilterInvocationDefinitionSourceListener.java,v 1.1 2005/08/09 01:47:18 Administrator Exp $ */ public class FilterInvocationDefinitionSourceListener implements ApplicationListener,InitializingBean { private static final Log logger = LogFactory.getLog(FilterInvocationDefinitionSourceListener.class); private FilterInvocationDefinitionSourceHolder definitionSourceHolder; /** * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) */ public void onApplicationEvent(ApplicationEvent event) { if (! FilterInvocationDefinitionSourceChangedEvent.class.isInstance(event)) { return; } if (logger.isDebugEnabled()) { logger.debug("Enter FilterInvocationDefinitionSourceListener, begin reload FilterInvocationDefinitionSource"); } getDefinitionSourceHolder().reload(); } /** * @return Returns the definitionSourceHolder. */ public FilterInvocationDefinitionSourceHolder getDefinitionSourceHolder() { return definitionSourceHolder; } /** * @param definitionSourceHolder The definitionSourceHolder to set. */ public void setDefinitionSourceHolder(FilterInvocationDefinitionSourceHolder sourceHolder) { this.definitionSourceHolder = sourceHolder; } public void afterPropertiesSet() throws Exception { Assert.notNull(getDefinitionSourceHolder(), " definitionSourceHolder must be specfied "); } } [/code:1] |
|
| 返回顶楼 | |
|
最后更新时间:2005-08-23
好帖,不过acegi0.82版本之前的扩展性不好,加一些特殊的authentication流就要写很多code,还改了源码。
|
|
| 返回顶楼 | |
|
最后更新时间:2005-08-23
我总感觉acegi动不动就一大把的filter,这样搞不会影响效率啊?速度能不受影响吗??我还是喜欢自己实现个。
|
|
| 返回顶楼 | |
|
最后更新时间:2005-08-23
引用 这样搞不会影响效率啊?
使用正则表达式比较的话,看url及表达式复杂度咯. 一般的1秒比较100万次都可以. |
|
| 返回顶楼 | |
|
最后更新时间:2005-08-25
我觉得acegi的objectDefinitionSource应该能够提供一个接收集合的方法,看楼主的实现中,for循环中new对象,实在是。。。
如果能够传入一个特定格式的hashmap不是更好么? |
|
| 返回顶楼 | |
|
最后更新时间:2005-08-25
to:wuhaixing
多谢指点。 to:Feiing 我原来也是如此想法,以为从数据库读资源只能实现一次读取,可是做了后简单测了一测,发现acegi似乎是动态读取了。我用了一个用户访问没有权限的页面,出来access deny错误后,到数据库添加一行权限记录,然后刷新,竟然过了。 不过还是比较欣赏你的实现方式,改天有空了一定按你的方式做个例子,比较比较。 to:flyromza 我比较喜欢用java类来承载有格式的数据。个人记性不太好,总忘了hashmap里面放的数据是什么格式,所以我一般能不用hashmap的时候都尽量不用了。你能说说"for里面new对象,实在是。。。"中的三个句号吗? |
|
| 返回顶楼 | |
|
最后更新时间:2005-08-25
[quote="bibitoo712"]to:wuhaixing
to:Feiing 我原来也是如此想法,以为从数据库读资源只能实现一次读取,可是做了后简单测了一测,发现acegi似乎是动态读取了。我用了一个用户访问没有权限的页面,出来access deny错误后,到数据库添加一行权限记录,然后刷新,竟然过了。 quote] [code:1] 不可能的, 除非你把 securityEnforcementFilter 和 filterInvocationInterceptor 都声明为 singleton = "false", 这个我已经做过很多次测试, 如果 singleton = "true" 的话, bean 加载时你的 filterInvocationInterceptor 中的 objectDefinitionSource 已经加载并被容器缓存起来, 所以不可能再变化, 但是把一个 filter 声明为 singleton = "false" 在我看来更不能接受, 所以比较好的方案就是覆盖 FilterInvocationDefinitionEditor, 通过自定义的 filterInvocationDefinitionSourceHolder 来提供 objectDefinitionSource , 然后在所有对 role 表, permission 表, resource 表 写数据时通过 observer 模式通知 filterInvocationDefinitionSourceHolder 重新读取资源权限数据 [/code:1] |
|
| 返回顶楼 | |
|
最后更新时间:2005-08-26
不错不错
支持一下 对了,我在web.xml里面用的是filter chain的配置方法,在context里面顺序列出filter,这样web.xml看起来简单一点 |
|
| 返回顶楼 | |











