??xml version="1.0" encoding="utf-8" standalone="yes"?>
Acegi 自带?sample 表设计很? users表{username,password,enabled} authorities表{username,authority},q样单的设计无法适应复杂的权限需?故SpringSide选用RBAC模型Ҏ限控制数据库表进行扩展?nbsp;RBAC引入了ROLE的概?使User(用户)和Permission(权限)分离,一个用h有多个角?一个角色拥有有多个相应的权?从而减了权限理的复杂度,可更灉|地支持安全策略?
同时,我们也引入了resource(资源)的概?一个资源对应多个权限,资源分ؓACL,URL,和FUNTION三种。注意,URL和FUNTION的权限命名需要以AUTH_开头才会有资格参加投票, 同样的ACL权限命名需要ACL_开头?/p>
在SpringSide里的 Acegi 扩展使用 EhCache ׃ZU缓存解x?以缓存用户和资源的信息和相对应的权限信息?/span>
首先需要一个在classpath?span style="font-family: Arial"> ehcache.xml 文gQ用于配|?/span> EhCache?/span>
<ehcache>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
diskPersistent="false"
diskExpiryThreadIntervalSeconds= "120"/>
<!-- acegi cache-->
<cache name="userCache"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk= "true"/>
<!-- acegi cache-->
<cache name="resourceCache"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk="true"/>
</ehcache>
maxElementsInMemory讑֮了允许在Cache中存攄数据数目Qeternal讑֮Cache是否会过期,overflowToDisk讑֮内存不的时候缓存到盘QtimeToIdleSeconds和timeToLiveSeconds讑֮~存游离旉和生存时_diskExpiryThreadIntervalSeconds讑֮~存在硬盘上的生存时_注意当eternal="true"ӞtimeToIdleSecondsQtimeToLiveSeconds和diskExpiryThreadIntervalSeconds都是无效的?/span>
<defaultCache>是除制定的Cache外其余所有Cache的设|,针对Acegi 的情? 专门讄了userCache和resourceCacheQ都设ؓ怸q期。在applicationContext-acegi-security.xml中相应的调用?/span>
<bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="cacheManager"/>
<property name="cacheName" value=" userCache"/>
</bean>
<bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache" autowire="byName">
<property name="cache" ref="userCacheBackend"/>
</bean>
<bean id="resourceCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="cacheManager"/>
<property name="cacheName" value=" resourceCache"/>
</bean>
<bean id="resourceCache" class="org.springside.modules.security.service.acegi.cache.ResourceCache" autowire="byName">
<property name="cache" ref="resourceCacheBackend"/>
</bean>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
"cacheName" 是讑֮?span lang="EN-US">ehcache.xml 中相应Cache的名U?/span>
userCache使用的是Acegi 的EhCacheBasedUserCache(实现了UserCache接口), resourceCache是SpringSide的扩展类
public interface UserCache {
public UserDetails getUserFromCache (String username);
public void putUserInCache (UserDetails user);
public void removeUserFromCache (String username);
}
public class ResourceCache {
public ResourceDetails getAuthorityFromCache (String resString) {... }
public void putAuthorityInCache (ResourceDetails resourceDetails) {... }
public void removeAuthorityFromCache (String resString) {... }
public List getUrlResStrings() {... }
public List getFunctions() {.. }
}
UserCache 是通过EhCache对UserDetails q行~存理, 而ResourceCache 是对ResourceDetails c进行缓存管?/span>
public interface UserDetails extends Serializable {
public boolean isAccountNonExpired();
public boolean isAccountNonLocked();
public GrantedAuthority[] getAuthorities();
public boolean isCredentialsNonExpired();
public boolean isEnabled();
public String getPassword();
public String getUsername();
}
public interface ResourceDetails extends Serializable {
public String getResString();
public String getResType();
public GrantedAuthority[] getAuthorities();
}
UserDetails 包含用户信息和相应的权限QResourceDetails 包含资源信息和相应的权限?/span>
public interface GrantedAuthority {
public String getAuthority ();
}
GrantedAuthority 是权限信息Q在Acegi ?sample ?span lang="EN-US" style="font-family: Arial">GrantedAuthority 的信息如ROLE_USER, ROLE_SUPERVISOR, ACL_CONTACT_DELETE, ACL_CONTACT_ADMIN{等Q网上也有很多例子把角色作ؓGrantedAuthority Q但事实上看看ACL q道, Acegi本nҎ没有角色这个概念,GrantedAuthority 包含的信息应该是权限Q对于非ACL的权限用 AUTH_ 开头更为合? 如SpringSide里的 AUTH_ADMIN_LOGIN, AUTH_BOOK_MANAGE {等?/span>
使用AcegiCacheManager?span lang="EN-US" style="font-family: Arial">userCache和resourceCacheq行l一~存理?font face="Tahoma">当在后台对用户信息进行修Ҏ赋权的时? 在更新数据库同时׃调用acegiCacheManager相应Ҏ, 从数据库中读取数据ƈ替换cache中相应部?使cache与数据库同步?/font>
public class AcegiCacheManager extends BaseService {
private ResourceCache resourceCache ;
private UserCache userCache ;
/**
* 修改User时更改userCache
*/
public void modifyUserInCache (User user, String orgUsername) {... }
/**
* 修改Resource时更改resourceCache
*/
public void modifyResourceInCache (Resource resource, String orgResourcename) {... }
/**
* 修改权限时同时修改userCache和resourceCache
*/
public void modifyPermiInCache (Permission permi, String orgPerminame) {... }
/**
* User授予角色时更改userCache
*/
public void authRoleInCache (User user) {... }
/**
* Role授予权限时更改userCache和resourceCache
*/
public void authPermissionInCache (Role role) {... }
/**
* Permissioni授予资源时更改resourceCache
*/
public void authResourceInCache (Permission permi) {... }
/**
* 初始化userCache
*/
public void initUserCache () {... }
/**
* 初始化resourceCache
*/
public void initResourceCache () {... }
/**
* 获取所有的url资源
*/
public List getUrlResStrings () {... }
/**
* 获取所有的Funtion资源
*/
public List getFunctions () {... }
/**
* Ҏ资源串获取资?
*/
public ResourceDetails getAuthorityFromCache (String resString) {... }
......
}
Acegil出的sample?资源权限对照关系是配|在xml中的,试想一下如果你的企业安全应用有500个用?100个角色权限的时?l护q个xml是个繁重无比的工作,如何动态更改用h限更是个头痛的问题?/p>
<bean id="contactManagerSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
<property name="afterInvocationManager"><ref local="afterInvocationManager"/></property>
<property name="objectDefinitionSource">
<value>
sample.contact.ContactManager.create=ROLE_USER
sample.contact.ContactManager.getAllRecipients=ROLE_USER
sample.contact.ContactManager.getAll=ROLE_USER,AFTER_ACL_COLLECTION_READ
sample.contact.ContactManager.getById=ROLE_USER,AFTER_ACL_READ
sample.contact.ContactManager.delete=ACL_CONTACT_DELETE
sample.contact.ContactManager.deletePermission=ACL_CONTACT_ADMIN
sample.contact.ContactManager.addPermission=ACL_CONTACT_ADMIN
</value>
</property>
</bean>
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/></property>
<property name="objectDefinitionSource">
<value>
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
</value>
</property>
</bean>
对如此不Pragmatic的做?SpringSideq行了扩? 让Acegi 能动态读取数据库中的权限资源关系?/p>
<bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="objectDefinitionSource" ref="methodDefinitionSource"/>
</bean>
<bean id="methodDefinitionSource" class="org.springside.security.service.acegi.DBMethodDefinitionSource">
<property name="acegiCacheManager" ref="acegiCacheManager"/>
</bean>
研究下Aceig的源?ObjectDefinitionSource的实际作用是q回一?strong>ConfigAttributeDefinition对象Q而Acegi Sample 的方式是?strong>MethodDefinitionSourceEditor把xml中的文本Function资源权限对应关系信息加蝲?strong>MethodDefinitionMap ( MethodDefinitionSource 的实现类 )? 再组成ConfigAttributeDefinitionQ而我们的扩展目标是从~存中读取信息来l成ConfigAttributeDefinition?/p>
MethodSecurityInterceptor是通过调用AbstractMethodDefinitionSource?strong>lookupAttributes(method)Ҏ获取ConfigAttributeDefinition。所以我们需要实现自qObjectDefinitionSourceQ?/font>l承AbstractMethodDefinitionSourceq实现其lookupAttributesҎ,从缓存中d资源权限对应关系l成q返回ConfigAttributeDefinition卛_。SpringSide中的DBMethodDefinitionSourcecȝ部分实现如下 :
public class DBMethodDefinitionSource extends AbstractMethodDefinitionSource {
......
protected ConfigAttributeDefinition lookupAttributes(Method mi) {
Assert.notNull(mi, "lookupAttrubutes in the DBMethodDefinitionSource is null");
String methodString = mi.getDeclaringClass().getName() + "." + mi.getName();
if (!acegiCacheManager.isCacheInitialized()) {
//初始化Cache
acegiCacheManager.initResourceCache();
}
//获取所有的function
List methodStrings = acegiCacheManager.getFunctions();
Set auths = new HashSet();
//取权限的合集
for (Iterator iter = methodStrings.iterator(); iter.hasNext();) {
String mappedName = (String) iter.next();
if (methodString.equals(mappedName)
|| isMatch(methodString, mappedName)) {
ResourceDetails resourceDetails = acegiCacheManager.getAuthorityFromCache(mappedName);
if (resourceDetails == null) {
break;
}
GrantedAuthority[] authorities = resourceDetails.getAuthorities();
if (authorities == null || authorities.length == 0) {
break;
}
auths.addAll(Arrays.asList(authorities));
}
}
if (auths.size() == 0)
return null;
ConfigAttributeEditor configAttrEditor = new ConfigAttributeEditor();
String authoritiesStr = " ";
for (Iterator iter = auths.iterator(); iter.hasNext();) {
GrantedAuthority authority = (GrantedAuthority) iter.next();
authoritiesStr += authority.getAuthority() + ",";
}
String authStr = authoritiesStr.substring(0, authoritiesStr.length() - 1);
configAttrEditor.setAsText(authStr);
//l装q返回ConfigAttributeDefinition
return (ConfigAttributeDefinition) configAttrEditor.getValue();
}
......
}
要注意几点的?
1) 初始化Cache是比较浪费资源的Q所以SpringSide中除W一ơ访问外的Cache的更新是针对性更新?/p>
2) 因ؓmethod采用了匹配方?详见 isMatch() Ҏ) , 卛_?Book和save*q两个资源来_只要当前讉KҎ是Bookl尾或以save开头都匹配得上,所以应该取q些能匹配上的资源的相对应的权限的合集?/p>
3) 使用ConfigAttributeEditor 能更方便地组装ConfigAttributeDefinition?
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="objectDefinitionSource" ref="filterDefinitionSource"/>
</bean>
<bean id="filterDefinitionSource" class="org.springside.security.service.acegi.DBFilterInvocationDefinitionSource">
<property name="convertUrlToLowercaseBeforeComparison" value="true"/>
<property name="useAntPath" value="true"/>
<property name="acegiCacheManager" ref="acegiCacheManager"/>
</bean>
PathBasedFilterInvocationDefinitionMap和RegExpBasedFilterInvocationDefinitionMap都是 FilterInvocationDefinitionSource的实现类,当PATTERN_TYPE_APACHE_ANT字符串匹配上时时,FilterInvocationDefinitionSourceEditor 选用PathBasedFilterInvocationDefinitionMap 把xml中的文本URL资源权限对应关系信息加蝲?/p>
FilterSecurityInterceptor通过FilterInvocationDefinitionSource?strong>lookupAttributes(url)Ҏ获取ConfigAttributeDefinition?所以,我们可以通过l承FilterInvocationDefinitionSource的抽象类AbstractFilterInvocationDefinitionSourceQƈ实现其lookupAttributesҎ,从缓存中dURL资源权限对应关系卛_。SpringSide?strong>DBFilterInvocationDefinitionSourcec部分实现如?
public class DBFilterInvocationDefinitionSource extends AbstractFilterInvocationDefinitionSource {
......
public ConfigAttributeDefinition lookupAttributes(String url) {
if (!acegiCacheManager.isCacheInitialized()) {
acegiCacheManager.initResourceCache();
}
if (isUseAntPath()) {
// Strip anything after a question mark symbol, as per SEC-161.
int firstQuestionMarkIndex = url.lastIndexOf("?");
if (firstQuestionMarkIndex != -1) {
url = url.substring(0, firstQuestionMarkIndex);
}
}
List urls = acegiCacheManager.getUrlResStrings();
//URL资源倒叙排序
Collections.sort(urls);
Collections.reverse(urls);
//是否先全部{为小写再比较
if (convertUrlToLowercaseBeforeComparison) {
url = url.toLowerCase();
}
GrantedAuthority[] authorities = new GrantedAuthority[0];
for (Iterator iterator = urls.iterator(); iterator.hasNext();) {
String resString = (String) iterator.next();
boolean matched = false;
//可选择使用AntPath和Perl5两种不同匚w模式
if (isUseAntPath()) {
matched = pathMatcher.match(resString, url);
} else {
Pattern compiledPattern;
Perl5Compiler compiler = new Perl5Compiler();
try {
compiledPattern = compiler.compile(resString,
Perl5Compiler.READ_ONLY_MASK);
} catch (MalformedPatternException mpe) {
throw new IllegalArgumentException(
"Malformed regular expression: " + resString);
}
matched = matcher.matches(url, compiledPattern);
}
if (matched) {
ResourceDetails rd = acegiCacheManager.getAuthorityFromCache(resString);
authorities = rd.getAuthorities();
break;
}
}
if (authorities.length > 0) {
String authoritiesStr = " ";
for (int i = 0; i < authorities.length; i++) {
authoritiesStr += authorities[i].getAuthority() + ",";
}
String authStr = authoritiesStr.substring(0, authoritiesStr
.length() - 1);
ConfigAttributeEditor configAttrEditor = new ConfigAttributeEditor();
configAttrEditor.setAsText(authStr);
return (ConfigAttributeDefinition) configAttrEditor.getValue();
}
return null;
}
......
}
l承AbstractFilterInvocationDefinitionSource注意几点Q?br /> 1) 需要先把获取回来的URL资源按倒序zֺQ以辑ֈ a/b/c/d.* ?a/.* 之前的效?详见 Acegi sample 的applicationContext-acegi-security.xml 中的filterInvocationInterceptor的注?Qؓ的是更具体的URL可以先匹配上Q而获取具体URL的权限,如a/b/c/d.*权限AUTH_a, AUTH_b 才可查看, a/.* 需要权限AUTH_a 才可查看Q则如果当前用户只拥有权限AUTH_b,则他只可以查看a/b/c/d.jsp 而不能察看a/d.jsp?/p>
2) Z上面的原因,故第一ơ匹配上的就是当前所需权限Q而不是取权限的合集?/p>
3) 可以选用AntPath ?Perl5 的资源匹配方式,感觉AntPath匚w方式基本_?/p>
4) Filter 权限控制比较适合于较_颗_度的权限,如设定某个模块下的页面是否能讉K{,对于具体某个操作如增删修改,是否能执行,用Method Invocation 会更佳些Q所以注意两个方面一h制效果更?/p>
RBAC模型中有不少多对多的关系Q这些关p都能以一个中间表的Ş式来存放Q而Hibernate中可以不中间表对应的hbm.xml , 以资源与权限的配|ؓ例,如下:
<hibernate-mapping package="org.springside.modules.security.domain">
<class name="Permission" table="PERMISSIONS" dynamic-insert="true" dynamic-update="true">
<cache usage="nonstrict-read-write"/>
<id name="id" column="ID">
<generator class="native"/>
</id>
<property name="name" column="NAME" not-null="true"/>
<property name="descn" column="DESCN"/>
<property name="operation" column="OPERATION"/>
<property name="status" column="STATUS"/>
<set name="roles" table="ROLE_PERMIS" lazy="true" inverse="true" cascade="save-update" batch-size="5">
<key>
<column name="PERMIS_ID" not-null="true"/>
</key>
<many-to-many class="Role" column="ROLE_ID" outer-join="auto"/>
</set>
<set name="resources" table="PERMIS_RESC" lazy="true" inverse="false" cascade="save-update" batch-size="5">
<key>
<column name="PERMIS_ID" not-null="true"/>
</key>
<many-to-many class="Resource" column="RESC_ID"/>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping package="org.springside.modules.security.domain">
<class name="Resource" table="RESOURCES" dynamic-insert="true" dynamic-update="true">
<cache usage="nonstrict-read-write"/>
<id name="id" column="ID">
<generator class="native"/>
</id>
<property name="name" column="NAME" not-null="true"/>
<property name="resType" column="RES_TYPE" not-null="true"/>
<property name="resString" column="RES_STRING" not-null="true"/>
<property name="descn" column="DESCN"/>
<set name="permissions" table="PERMIS_RESC" lazy="true" inverse="true" cascade="save-update" batch-size="5">
<key>
<column name="RESC_ID" not-null="true"/>
</key>
<many-to-many class="Permission" column="PERMIS_ID" outer-join="auto"/>
</set>
</class>
</hibernate-mapping>
配置时注意几?
1) 因ؓ是分配某个权限的资源Q所以权限是L方,把inverse设ؓfalseQ资源是被控方inverse设ؓtrue
2) cascade?save-update"Q千万别配成delete
3) 只需?permission.getResources().add(resource)Q?permission.getResources()..remove(resource) 卛_很方便地完成授权和取消授权操?/p>
1) FilterToBeanProxy
Acegi通过实现了Filter接口的FilterToBeanProxy提供一U特D的使用Servlet Filter的方式,它委托Spring中的Bean -- FilterChainProxy来完成过滤功能,q好处是化了web.xml的配|,q且充分利用了Spring IOC的优ѝFilterChainProxy包含了处理认证过E的filter列表Q每个filter都有各自的功能?/p>
<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
2) filter-mapping
<filter-mapping>限定了FilterToBeanProxy的URL匚w模式,只有*.do?.jsp?j_acegi_security_check 的请求才会受到权限控Ӟ对javascript,css{不限制?/p>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/j_acegi_security_check</url-pattern>
</filter-mapping>
3) HttpSessionEventPublisher
<listener>的HttpSessionEventPublisher用于发布HttpSessionApplicationEvents和HttpSessionDestroyedEvent事glspring的applicationcontext?/p>
<listener>
<listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
FilterChainProxy会按序来调用这些filter,使这些filter能n用Spring ioc的功? CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON定义了url比较前先转ؓ写Q?PATTERN_TYPE_APACHE_ANT定义了用Apache ant的匹配模?
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,
basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,
exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
1) authenticationManager
起到认证理的作用,它将验证的功能委托给多个ProviderQƈ通过遍历Providers, 以保证获取不同来源的w䆾认证Q若某个Provider能成功确认当前用Lw䆾Qauthenticate()Ҏ会返回一个完整的包含用户授权信息的Authentication对象Q否则会抛出一个AuthenticationException?br />
Acegi提供了不同的AuthenticationProvider的实?如:
DaoAuthenticationProvider 从数据库中读取用户信息验证n?br />
AnonymousAuthenticationProvider 匿名用户w䆾认证
RememberMeAuthenticationProvider 已存cookie中的用户信息w䆾认证
AuthByAdapterProvider 使用容器的适配器验证n?br />
CasAuthenticationProvider ҎYale中心认证服务验证w䆾, 用于实现单点登陆
JaasAuthenticationProvider 从JASS登陆配置中获取用户信息验证n?br />
RemoteAuthenticationProvider Ҏq程服务验证用户w䆾
RunAsImplAuthenticationProvider 对n份已被管理器替换的用戯行验?br />
X509AuthenticationProvider 从X509认证中获取用户信息验证n?br />
TestingAuthenticationProvider 单元试时?/p>
每个认证者会对自己指定的证明信息q行认证Q如DaoAuthenticationProvider仅对UsernamePasswordAuthenticationTokenq个证明信息q行认证?/p>
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="anonymousAuthenticationProvider"/>
<ref local="rememberMeAuthenticationProvider"/>
</list>
</property>
</bean>
2) daoAuthenticationProvider
q行单的Z数据库的w䆾验证。DaoAuthenticationProvider获取数据库中的̎号密码ƈq行匚wQ若成功则在通过用户w䆾的同时返回一个包含授权信息的Authentication对象Q否则n份验证失败,抛出一个AuthenticatiionException?/p>
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="jdbcDaoImpl"/>
<property name="userCache" ref="userCache"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
</bean>
3) passwordEncoder
使用加密器对用户输入的明文进行加密。Acegi提供了三U加密器:
PlaintextPasswordEncoder—默认,不加密,q回明文.
ShaPasswordEncoder—哈希算?SHA)加密
Md5PasswordEncoder—消息摘?MD5)加密
<bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/>
4) jdbcDaoImpl
用于在数据中获取用户信息?acegi提供了用户及授权的表l构Q但是您也可以自己来实现。通过usersByUsernameQueryq个SQL得到你的(用户ID,密码,状态信?;通过authoritiesByUsernameQueryq个SQL得到你的(用户ID,授权信息)
<bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
<property name="usersByUsernameQuery">
<value>select loginid,passwd,1 from users where loginid = ?</value>
</property>
<property name="authoritiesByUsernameQuery">
<value>select u.loginid,p.name from users u,roles r,permissions p,user_role ur,role_permis rp where u.id=ur.user_id and r.id=ur.role_id and p.id=rp.permis_id and
r.id=rp.role_id and p.status='1' and u.loginid=?</value>
</property>
</bean>
5) userCache & resourceCache
~存用户和资源相对应的权限信息。每当请求一个受保护资源ӞdaoAuthenticationProvider׃被调用以获取用户授权信息。如果每ơ都从数据库获取的话Q那代h很高Q对于不常改变的用户和资源信息来_最好是把相x权信息缓存v来?详见 2.6.3 资源权限定义扩展 )
userCache提供了两U实? NullUserCache和EhCacheBasedUserCache, NullUserCache实际上就是不q行M~存QEhCacheBasedUserCache是用Ehcache来实现缓功能?/p>
<bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="cacheManager"/>
<property name="cacheName" value="userCache"/>
</bean>
<bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache" autowire="byName">
<property name="cache" ref="userCacheBackend"/>
</bean>
<bean id="resourceCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="cacheManager"/>
<property name="cacheName" value="resourceCache"/>
</bean>
<bean id="resourceCache" class="org.springside.modules.security.service.acegi.cache.ResourceCache" autowire="byName">
<property name="cache" ref="resourceCacheBackend"/>
</bean>
6) basicProcessingFilter
用于处理HTTP头的认证信息Q如从Springq程协议(如Hessian和Burlap)或普通的览器如IE,Navigator的HTTP头中获取用户信息Q将他们转交l通过authenticationManager属性装配的认证理器。如果认证成功,会将一个Authentication对象攑ֈ会话中,否则Q如果认证失败,会将控制转交l认证入口点(通过authenticationEntryPoint属性装?
<bean id="basicProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationEntryPoint" ref="basicProcessingFilterEntryPoint"/>
</bean>
7) basicProcessingFilterEntryPoint
通过向浏览器发送一个HTTP401(未授?消息Q提C用L录?br />
处理ZHTTP的授权过E, 在当验证q程出现异常后的"d"Q通常实现转向、在response里加入error信息{功能?/p>
<bean id="basicProcessingFilterEntryPoint" class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
<property name="realmName" value="SpringSide Realm"/>
</bean>
8) authenticationProcessingFilterEntryPoint
当抛出AccessDeniedExceptionӞ用户重定向到登录界面。属性loginFormUrl配置了一个登录表单的URL,当需要用L录时QauthenticationProcessingFilterEntryPoint会将用户重定向到该URL
<bean id="authenticationProcessingFilterEntryPoint" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl">
<value>/security/login.jsp</value>
</property>
<property name="forceHttps" value="false"/>
</bean>
1) httpSessionContextIntegrationFilter
每次request?HttpSessionContextIntegrationFilter从Session中获取Authentication对象Q在request完后, 又把Authentication对象保存到Session中供下次request使用,此filter必须其他Acegi filter前用,使之能跨多个请求?/p>
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"></bean>
<bean id="httpRequestAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<list>
<ref bean="roleVoter"/>
</list>
</property>
</bean>
2) httpRequestAccessDecisionManager
l过投票机制来决定是否可以访问某一资源(URL或方?。allowIfAllAbstainDecisions为false时如果有一个或以上的decisionVoters投票通过,则授权通过。可选的决策机制有ConsensusBased和UnanimousBased
<bean id="httpRequestAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<list>
<ref bean="roleVoter"/>
</list>
</property>
</bean>
3) roleVoter
必须是以rolePrefix讑֮的value开头的权限才能q行投票,如AUTH_ , ROLE_
<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter">
<property name="rolePrefix" value="AUTH_"/>
</bean>
4Q?strong>exceptionTranslationFilter
异常转换qo器,主要是处理AccessDeniedException和AuthenticationExceptionQ将l每个异常找到合适的"d"
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="authenticationProcessingFilterEntryPoint"/>
</bean>
5) authenticationProcessingFilter
和servlet spec差不?处理登陆h.当n份验证成功时QAuthenticationProcessingFilter会在会话中放|一个Authentication对象Qƈ且重定向到登录成功页?br />
authenticationFailureUrl定义登陆p|时{向的面
defaultTargetUrl定义登陆成功时{向的面
filterProcessesUrl定义登陆h的页?br />
rememberMeServices用于在验证成功后dcookie信息
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl">
<value>/security/login.jsp?login_error=1</value>
</property>
<property name="defaultTargetUrl">
<value>/admin/index.jsp</value>
</property>
<property name="filterProcessesUrl">
<value>/j_acegi_security_check</value>
</property>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean>
6) filterInvocationInterceptor
在执行{向url前检查objectDefinitionSource中设定的用户权限信息。首先,objectDefinitionSource中定义了讉KURL需要的属性信?q里的属性信息仅仅是标志Q告诉accessDecisionManager要用哪些voter来投?。然后,authenticationManager掉用自己的provider来对用户的认证信息进行校验。最后,有投者根据用h有认证和讉Kurl需要的属性,调用自己的voter来投,军_是否允许讉K?/p>
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="objectDefinitionSource" ref="filterDefinitionSource"/>
</bean>
7) filterDefinitionSource (详见 2.6.3 资源权限定义扩展)
自定义DBFilterInvocationDefinitionSource从数据库和cache中读取保护资源及光要的讉K权限信息
<bean id="filterDefinitionSource" class="org.springside.modules.security.service.acegi.DBFilterInvocationDefinitionSource">
<property name="convertUrlToLowercaseBeforeComparison" value="true"/>
<property name="useAntPath" value="true"/>
<property name="acegiCacheManager" ref="acegiCacheManager"/>
</bean>
(详见 2.6.3 资源权限定义扩展)
1) methodSecurityInterceptor
在执行方法前q行拦截Q检查用h限信?br />
2) methodDefinitionSource
自定义MethodDefinitionSource从cache中读取权?/p>
<bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="objectDefinitionSource" ref="methodDefinitionSource"/>
</bean>
<bean id="methodDefinitionSource" class="org.springside.modules.security.service.acegi.DBMethodDefinitionSource">
<property name="acegiCacheManager" ref="acegiCacheManager"/>
</bean>
<bean id="bankManagerSecurity" class="net.sf.acegisecurity.intercept.method.MethodSecurityInterceptor">
<property name="validateConfigAttributes">
<value>true</value>
</property>
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="accessDecisionManager">
<ref bean="accessDecisionManager"/>
</property>
<property name="objectDefinitionSource">
<value>net.sf.acegisecurity.context.BankManager.delete*=
ROLE_SUPERVISOR,RUN_AS_SERVER
net.sf.acegisecurity.context.BankManager.getBalance=
ROLE_TELLER,ROLE_SUPERVISOR,BANKSECURITY_CUSTOMER,RUN_
</value>
</property>
</bean>
<bean id="authenticationDao" 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 bean="authenticationDao"/></property>
</bean>
<bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">
<property name="providers">
<list><ref bean="daoAuthenticationProvider"/></list>
</property>
</bean>
<bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>
<bean id="accessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions"><value>false</value></property>
<property name="decisionVoters">
<list><ref bean="roleVoter"/></list>
</property>
</bean>
q个例子使用了HSQL做数据库Qspring的AOP作ؓ基础Q用Acegi做安全控制组件?br />
联系人管理的web应用在启动时候,会做一pd初始化动作:
1. dweb.xml文gQ?/p>
2. q解析文仉的内宏V?br />
a) context-param元素?br />
i. contextConfigLocation属性。这个属性定义了spring所需要的3个属性文件。它们分别是QapplicationContext -acegi-security.xml、applicationContext-common-business.xml?applicationContext-common-authorization.xml
ii. log4jConfigLocation属性。这个属性定义了log4j配置文g?/p>
b) filter元素?br />
q里定义了acegi的一个过滤器。Acegi的大部分qo器都是这样配|的。用FilterToBeanProxylgQ给它传递一个targetClass属性。这个targetClass必须实现javax.servlet.Filter接口?br />
q里配置的是FilterChainProxy。这个FilterChainProxy比较好用Q可以ؓ它定义一串filter属性。这些filter会按照定义的顺序被调用。例如,
<bean id="filterChainProxy" class="net.sf.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter
</value>
</property>
</bean>
q个qo器的mapping?#8220;/*”?br />
c) listener元素?br />
i. ContextLoaderListener。这个是Spring使用来加载根applicationcontext。ƈ分别解析 applicationContext-acegi-security.xml、applicationContext-common- business.xml、applicationContext-common-authorization.xml{配|文Ӟ把相关的对象初始?br />
iii. Log4jConfigListener。这个是spring用来初始化log4jlg的listener?br />
iv. HttpSessionEventPublisher。这个组件将发布HttpSessionCreatedEvent和HttpSessionDestroyedEvent事glspring的applicationcontext?br />
d) servlet元素?br />
i. contacts。这里采用了spring的MVC框架Q?所以这个servlet是spring MVC的一个核心控制器Qorg.springframework.web.servlet.DispatcherServletQ。这个servlet 启动时候,会从contacts-servlet.xml里面d信息Qƈ做相关的初始化?br />
v. remoting。也是spring MVC的一个核心控制器。与contacts不同Q这个servlet主要是提供web services服务。这个servlet启动时候, 会从remoting-servlet.xml里面d信息Qƈ做相关的初始化?br />
e) taglib元素。这里定义了spring的标f) {ֺ?br />
3. 解析applicationContext-acegi-security.xml?br />
a) qo器链。定义了一个FilterChainProxyQb) q指c) 定了一pd的过滤器链。httpSessionContextIntegrationFilter, authenticationProcessingFilter,basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter
d) 认证理器。这个管理器由acegi提供。这个管理器需要一个providers参数。这个providers参数包含了提供系l认证的对象?br />
i. daoAuthenticationProvider。一般用戯证?br />
ii. anonymousAuthenticationProvider。匿名用戯证?br />
iv. rememberMeAuthenticationProvider。记住我认证?/p>
e) 密码加密。这里定义了一个acegi的Md5法加密对象Md5PasswordEncoder?br />
f) 定义了一个jdbcDao实现cR这个类由acegi提供的net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl。这个对象需要一个dataSource的参数?br />
g) 定义daoAuthenticationProvider。这个对象由acegi提供。它?个属性:
authenticationDao。这里指向前面定义的jdbcDao?br />
userCache。这里指向后面定义的user~存对象?br />
passwordEncoder。这里指向前面定义的密码加密对象?br />
h) 用户~存理?br />
Z~存userQ这里用spring的ehcache来缓存user。缓存机Ӟ
i. 定义~存理器――CacheManager。这个对象是spring的EhCacheManagerFactoryBean对象
ii. 定义user~存实际执行对象――UserCacheBackend。这个对象是spring的EhCacheFactoryBean。它有两个属性:
1. cacheManager。这里指向前面定义的~存理器?br />
2. cacheName?br />
iii. 定义user~存――UserCache。它是acegi提供的EhCacheBasedUserCache对象。它有一个属性:
1. cache。这里指向的是前面定义的userCacheBackend?/p>
i) 定义接收来自DaoAuthenticationProvider的认证事件的listener――LoggerListener?br />
j)
4. 解析applicationContext-common-business.xml?br />
a) dataSource.
q里使用了spring的DriverManagerDataSource对象。这个对象是一个JDBC数据源的定义?br />
b) TransactionManager。这里用spring的DataSourceTransactionManager对象?br />
c) 事务拦截器。这里用spring的事务拦截器TransactionInterceptor。它?个属性:
transactionManager。这个属性指向前面定义的TransactionManager?br />
transactionAttributeSource。这个属性里Q?指定了ContactManager的各个方法的事务斚w的要求?br />
d) DataSourcePopulator?br />
使用sample.contact.DataSourcePopulator对象Q往HSQL里创建相关的表结构和数据?br />
实现原理QDataSourcePopulator 实现了接?InitializingBean。其中afterPropertiesSetҎ在spring初始化DataSourcePopulator后被调用?br />
e) ContactDao。这里指向一个ContactDaoSpring对象。它l承spring?JdbcDaoSupportQg) q实现ContactDao接口。它是真正实现JDBC操作的对象?br />
h) ContactManager。这里用的是spring的ProxyFactoryBean。它?个属性:
i. ProxyInterfaces。代理接口:sample.contact.ContactManager
ii. InterceptorNames。拦截器名称。可以有多个Qiv. q里包括QtransactionInterceptor、contactManagerSecurity、contactManagerTarget。其中,v. transactionInterceptor是前面定义的事务拦截器。ContactManagerSecurity则是?applicationContext-common-authorization.xml里定义的Ҏ调用授权?br />
i) ContactManagerTarget。这里指向的是sample.contact.ContactManagerBackend对象?ContactManagerBackend实现了ContactManager接口和InitializingBean接口。它?个自定义属性: contactDao和basicAclExtendedDao。这里会调用ACL的APId些创建权限和删除权限的工作?/p>
联系人管理说明了下列中心?span lang="EN-US">Acegi安全控制能力:
ContactManager
服务层对?/span> 包含一些受保护的和公开的方法?/span>
/secure
”URI路径被?/span>Acegi安全保护Q得没?/span>ROLE_USER
角色的用h法访问?/span>.
联系人管理的业务功能描述Q?/span>
1. 每个用户d后,可以看到一个联pMh列表。例如,
id |
Name |
|
||
1 |
John Smith |
john@somewhere.com |
||
2 |
Michael Citizen |
michael@xyz.com |
|
|
3 |
Joe Bloggs |
joe@demo.com |
|
|
4 |
Karen Sutherland |
karen@sutherland.com |
说明Q用h有权限访问的联系Z息,不会显C?/span>
2. 用户可以增加新的联系Z息?/span>
3. 如果有删除权限,用户可以看到在联pMh后面有一?#8220;Del”链接。用户可以点击这个链接来删除某个联系Z息?/span>
4. 如果有管理权限,用户可以看到在联pMh后面有一?#8220;Admin Permission”链接。用户可以点击这个链接来理讉Kq个联系人的权限。例如,
sample.contact.Contact@26807f: Id: 1; Name: John Smith; Email: john@somewhere.com
|
|
|
|
|
说明Q每一行记录包含有3列?/span>
W一列表C权限,例如Q?#8220;-RW-D
”表示可读、可写、可删除?/span>
W二列也表示权限Q但它是以类?/span>unix权限的数字表达。例如,“[22]”, 表示可读、可写、可删除?/span>
W三列是用户名称?/span>
每一行记录后面都有一?#8220;Del”链接。点击这个链接,可以删除掉指定用户对q个联系Z息的权限?/span>
5. 用户可以为某个联pMh信息d权限。例如,