项目上有用到Acegi,而项目上从不会教技术之类的,只好自己看源码。断断续续几个月时间。开始有些眉目。虽然我后面知道Acegi早已经过时了(难怪里面代码难懂,各种代码乱飞,莫非是没人维护的原因?),但是既然开始了,我还是想有始有终。
(现在做类似的项目真是悲哀,从没有人讲技术或是业务,就只是给你一个任务,你自己去搞,最终的代码的质量只能靠不断的无比重复烦闷低效的各种的测试来保证,测试不到的就只能是让维护交接的人悲剧了。开发人员能否学到东西?项目经理似乎从来不管,最终只有靠自己了!唉,我觉得项目组安排讲讲技术架构啊、业务流程啊肯定是有利于代码质量的,而且是肯定能提高开发效率的,也能让coder不再是单纯的coder,能够有所提升。可惜从来没有。唉,我要是项目经理一定不能这样啊!)
Acegi是用java开发的一套安全认证的框架,所谓java程序安全呢,其实主要就是认证和授权。
Acegi主要是由一系列的过滤器或拦截器组成。
首先的一个就是FilterToBeanProxy,它有一个targetClass属性,通常就用Acegi提供的FilterChainProxy,FilterChainProxy有属性:filterInvocationDefinitionSource,这是相当重要的一个属性,在它里面我们配置所有我们需要的的过滤器拦截器,
Acegi源码和spring绑定,所以只能在配合spring使用。感觉还是要非常属性spring才行,否则真是没法看下去。
而且名字一个个长的要死,看了一下源码,真晕,看不太懂耶,还是没多少耐心吧,唉!(我是用java decompiler 看的,感觉这个软件还是不太人性化,不好用,没eclipse好啊)
一般来说,有:
session过滤器:HttpSessionContextIntegrationFilter(是用来把认证信息记录到Session中的)
注销处理过滤器:LogoutFilter
表单认证过滤器:AuthenticationProcessingFilter
cookie登录过滤器:RememberMeProcessingFilter
匿名认证过滤器:AnonymousProcessingFilter
异常转换过滤器:ExceptionTranslationFilter
用户授权拦截器:FilterSecurityInterceptor
最关键的是两大管理器:
身份认证管理器ProviderManager对应org.acegisecurity.providers.ProviderManager
资源授权管理器accessDecisionManager对应org.acegisecurity.vote.AffirmativeBased
下面从FilterChainProxy开始为大家讲讲自己的心得,也望大家多多给我评论:意见、建议或者鼓励,写这篇文章确实是花了不少心血的。
:::
FilterChainProxy继承Filter
它主要有类型为FilterInvocationDefinitionSource的属性filterInvocationDefinitionSource;
类型为ApplicationContext的applicationContext,applicationContext当然就是结合spring使用的。
init
init函数里面调用obtainAllDefinedFilters,读取配置,获取其下面的各个filter,然后执行各自的
init之前,当然,这还要先初始化了filterInvocationDefinitionSource;filterInvocationDefinitionSource的初始化是由spring调用的,
FilterInvocationDefinitionSource空继承ObjectDefinitionSource接口,主要有三个方法:
public abstract interface ObjectDefinitionSource
{
public abstract ConfigAttributeDefinition getAttributes(Object paramObject)
throws IllegalArgumentException;
public abstract Iterator getConfigAttributeDefinitions();
public abstract boolean supports(Class paramClass);
}
ConfigAttributeDefinition主要有一个configAttributes的属性:
private List configAttributes = new Vector();
主要包括addConfigAttribute、getConfigAttributes、contains、equals等方法
addConfigAttribute就是将一个ConfigAttribute添加到configAttributes里面去,getConfigAttributes
很明显是获取configAttributes
ConfigAttribute,其实只是一个很简单的接口,就一个方法getAttribute,其实现主要有
SecurityConfig后面我们将知道,其实SecurityConfig在acegi里面就是用来封装角色的:
defn.addConfigAttribute(new SecurityConfig(resourceRole.getRole()));
doFilter
之后每当有request的时候,调用FilterChainProxy的doFilter:
首先将请求封装成FilterInvocation fi,
然后根据fi检查是否有配置过滤器,如果无,则直接把request交给chain,结束自己的过滤
否则,调用自身的内部类,VirtualFilterChain implements FilterChain进行依次过滤
下面说说重量级的FilterSecurityInterceptor,它是典型的filter
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter
doFilter
同样的,它首先将请求封装成FilterInvocation fi,然后调用自身的invoke(FilterInvocation fi)方法:
public void invoke(FilterInvocation fi)
throws IOException, ServletException
{
if ((fi.getRequest() != null) && (fi.getRequest().getAttribute("__acegi_filterSecurityInterceptor_filterApplied") != null) && (this.observeOncePerRequest))
{
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
if (fi.getRequest() != null) {
fi.getRequest().setAttribute("__acegi_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try
{
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
}
首先检查request里面是否有 __acegi_filterSecurityInterceptor_filterApplied属性、及自身的observeOncePerRequest(是否对每个request立即检查权限)属性,
是则——表明有权限,继续其他的过滤。
否则——先将__acegi_filterSecurityInterceptor_filterApplied设置成request的属性,然后
InterceptorStatusToken token = super.beforeInvocation(fi);
最后
super.afterInvocation(token, null);
然后返回InterceptorStatusToken,它标志了我们的具体过滤结果
关键就是super.beforeInvocation,super就是父类AbstractSecurityInterceptor,其beforeInvocation
签名是:
protected InterceptorStatusToken beforeInvocation(Object object) {
可见,其对参数的宽松,不过检查却是可谓有千百道工序:
//获取所有的fileter定义
ConfigAttributeDefinition attr = obtainObjectDefinitionSource().getAttributes(object);
然后是SecurityContextHolder的检查
如果没异常的话是,然后是accessDecisionManager的检查。
accessDecisionManager,即授权管理器,是由__spring___设置的
调用其decide方法,看此request是否有权限,:
this.accessDecisionManager.decide(authenticated, object, attr);
如果成功的话,执行RunAsManager相关处理
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr);
如果RunAsManager不为null则将runAs的Authentication放入SecurityContextHolder里:
SecurityContextHolder.getContext().setAuthentication(runAs);
很明显,这个方法主要就是accessDecisionManager的decide了,它直接决定了最终的过滤结果,下面我们仔细看看这个decide的写法:
accessDecisionManager其实FilterInvocationInterceptor的一个属性
accessDecisionManager对应类AffirmativeBased,它可以理解为一个决策处理器,它又由众多voter构成(即投票者们) ,遵循一定的投票规矩完成投票,最后选出投票通过者:
通常规则:如果:“是否让全部弃权的通过”为true,那么就是说,只要没有否决票就算通过(容许弃权票),否则只有全部都是赞成票才通过(不容许弃权票)
通常呢,我们只要一个投票着即可:RoleVoter
RoleVoter继承AccessDecisionVoter接口:
public abstract interface AccessDecisionVoter
{
public static final int ACCESS_GRANTED = 1;
public static final int ACCESS_ABSTAIN = 0;
public static final int ACCESS_DENIED = -1;
public abstract boolean supports(ConfigAttribute paramConfigAttribute);
public abstract boolean supports(Class paramClass);
public abstract int vote(Authentication paramAuthentication, Object paramObject, ConfigAttributeDefinition paramConfigAttributeDefinition);
}
主要,也是精华,就是vote方法,他表示单个投票者voter对单个request事件具体的投票过程,理解这一点相当关键。具体是:
public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
int result = 0;
Iterator iter = config.getConfigAttributes();
while (iter.hasNext()) {
ConfigAttribute attribute = (ConfigAttribute)iter.next();
if (supports(attribute)) {
result = -1;
for (int i = 0; i < authentication.getAuthorities().length; ++i) {
if (attribute.getAttribute().equals(authentication.getAuthorities()[i].getAuthority())) {
return 1;
}
}
}
}
—————— 获取当前登录用户的所有角色,然后跟访问目标资源所需的角色对比,只要有一个是相同的,就表示投票赞成。!!!
另有一个voter,AuthenticatedVoter一般用不上
具体的代码行进流程实在是千回百转,具体就不说了,下面说说其中一些重要的接口和类
AuthenticationTrustResolver接口:
public abstract boolean isAnonymous(Authentication paramAuthentication);
public abstract boolean isRememberMe(Authentication paramAuthentication);
实现类是:AuthenticationTrustResolverImpl
有两个重要属性:
this.anonymousClass = AnonymousAuthenticationToken.class;
this.rememberMeClass = RememberMeAuthenticationToken.class;
Authentication无疑是一个非常重要的接口:
public abstract interface Authentication extends Principal, Serializable
{
public abstract GrantedAuthority[] getAuthorities();
public abstract Object getCredentials();
public abstract Object getDetails();
public abstract Object getPrincipal();
public abstract boolean isAuthenticated();
public abstract void setAuthenticated(boolean paramBoolean)
throws IllegalArgumentException;
}
感觉GrantedAuthority[]对应了当前登录人的所有角色
getCredentials是获取密码
getPrincipal是获取用户名
isAuthenticated当然就是表明当前用户是否验证通过,是否是有效用户
getDetails获取用户详细资料
Principal是java.security包下的类
说道Authentication,就不得不说GrantedAuthority,感觉它就是对应了角色
public abstract interface GrantedAuthority extends Serializable
{
public abstract String getAuthority();
}
实现类GrantedAuthorityImpl,主要属性role,方法:
public String getAuthority() {
return this.role;
}
可见角色role即权限Authority。开始的时候一直不知道,非常搞不懂acegi源码,总是糊里糊涂啊,都是这外国人的文字游戏害的......
关于Token,从google翻译上得知:
名词
象征 symbol, token, sign, emblem, byword
符记 token
代币 token
地铁硬币 token
动词
象征 symbolize, token, signify
形容词
象征性的 token
表意的 ideographic, token, notional
其实可以理解为标记的意思吧,acegi里面有很多的token,其实都是一些简单的属性(用户名啊密码啊是否匿名啊等)封装标记类
这里的token都继承AbstractAuthenticationToken,从继承于Authentication (开始真不知道!....),Authentication 前面已经讲过。
UsernamePasswordAuthenticationToken 很明显,封装了Username、Password
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, GrantedAuthority[] authorities)
AnonymousAuthenticationToken 多维护一个keyHash字段,封装了principal和对应特定匿名用户的keyHash
public Object getCredentials()
{
return "";//以""作为credentials
}
投票是由voter完成的,但投票之前的准备工作还有很多:
如何获取登录者身份?
如何获取登录者权限?
acegi提供了多种方式,一般来说数据库方式是通过JdbcDaoImpl
如何确定某个角色的具体权限?
acegi提供了多种方式,一般来说数据库方式是通过FilterInvocationDefinitionSource
通常我们需要重写上面的两个类以完成我们的客制化。
JdbcDaoImpl里面主要方法就是loadUserByUsername:
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException
{
List users = this.usersByUsernameMapping.execute(username);
if (users.size() == 0) {
throw new UsernameNotFoundException("User not found");
}
UserDetails user = (UserDetails)users.get(0);
List dbAuths = this.authoritiesByUsernameMapping.execute(user.getUsername());
addCustomAuthorities(user.getUsername(), dbAuths);
if (dbAuths.size() == 0) {
throw new UsernameNotFoundException("User has no GrantedAuthority");
}
GrantedAuthority[] arrayAuths = (GrantedAuthority[])(GrantedAuthority[])dbAuths.toArray(new GrantedAuthority[dbAuths.size()]);
String returnUsername = user.getUsername();
if (!(this.usernameBasedPrimaryKey)) {
returnUsername = username;
}
return new User(returnUsername, user.getPassword(), user.isEnabled(), true, true, true, arrayAuths);
}
List users = this.usersByUsernameMapping.execute(username);//查询身份
UserDetails user = (UserDetails)users.get(0);//不管结果数几个,我们只取第一个,并放入表明用户信息的UserDetails 里面
List dbAuths = this.authoritiesByUsernameMapping.execute(user.getUsername());//查询权限
GrantedAuthority[] arrayAuths = (GrantedAuthority[])(GrantedAuthority[])dbAuths.toArray(new GrantedAuthority[dbAuths.size()]);//将对应角色放入GrantedAuthority[]里
User是一个用户信息类,包括了(returnUsername, Password,Enabled,arrayAuths,可见它是一个完整的用户信息类
addCustomAuthorities为空实现,从名字可见,它通常是有待实现的
通常,我们会保留有两个重要字段:
usersByUsernameQuery
authoritiesByUsernameQuery
前者,根据用户名从数据库查询密码,enabled——用于身份验证
后者,根据用户名从数据库查询role——对应所有的role,代表了对应的权限,用于授权验证
有人说查询的字段顺序是固定的,其实不然,只要查询出来,后面怎么处理是由开发者实现的。
//User实现了接口UserDetails:
public abstract interface UserDetails extends Serializable
{
public abstract GrantedAuthority[] getAuthorities();
public abstract String getPassword();
public abstract String getUsername();
public abstract boolean isAccountNonExpired();
public abstract boolean isAccountNonLocked();
public abstract boolean isCredentialsNonExpired();
public abstract boolean isEnabled();
}
FilterInvocationDefinitionSource空继承于接口ObjectDefinitionSource:
public abstract interface ObjectDefinitionSource
{
public abstract ConfigAttributeDefinition getAttributes(Object paramObject)
throws IllegalArgumentException;
public abstract Iterator getConfigAttributeDefinitions();
public abstract boolean supports(Class paramClass);
}
它有两个主要的实现:
RegExpBasedFilterInvocationDefinitionMap
PathBasedFilterInvocationDefinitionMap,
分别用在____的场合
PathBasedFilterInvocationDefinitionMap继承AbstractFilterInvocationDefinitionSource,后者又实现了FilterInvocationDefinitionSource,又继承ObjectDefinitionSource
getAttributes由后者实现,它获取request的url,然后将其作为参数调用前者的lookupAttributes方法,所以,可以说,差不多都是前者实现的
PathBasedFilterInvocationDefinitionMap有主要属性requestMap,
getAttributes意思就是判断request的url是否在配置的资源里面,是则组装成ConfigAttributeDefinition返回,另外可见这个ConfigAttributeDefinition也是多用途,配置属性定义?
这里面涉及EntryHolder
主要封装了下面的属性:
private ConfigAttributeDefinition configAttributeDefinition;
private Pattern compiledPattern;
从
definitionSource.addSecureUrl(resources.get(i).getResource(), defn);
可以看出compiledPattern对应(resource,而ConfigAttributeDefinition对应role数组
所以EntryHolder表明了单个资源(一般就是url,带有通配符的url)及对应的有权限的角色
getAttributes:
public ConfigAttributeDefinition lookupAttributes(String url) {
PatternMatcher matcher = new Perl5Matcher();
Iterator iter = this.requestMap.iterator();
if (isConvertUrlToLowercaseBeforeComparison()) {
url = url.toLowerCase();
if (logger.isDebugEnabled()) {
logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");
}
}
while (iter.hasNext()) {
EntryHolder entryHolder = (EntryHolder)iter.next();
boolean matched = matcher.matches(url, entryHolder.getCompiledPattern());
if (logger.isDebugEnabled()) {
logger.debug("Candidate is: '" + url + "'; pattern is " +
entryHolder.getCompiledPattern().getPattern() + "; matched=" + matched);
}
if (matched) {
return entryHolder.getConfigAttributeDefinition();
}
}
return null;
}
可见getAttributes是返回了单条的ConfigAttributeDefinition
而getConfigAttributeDefinitions返回全部ConfigAttributeDefinition的迭代器
addSecureUrl是重要方法
ConcurrentSessionController
public abstract interface ConcurrentSessionController
{
public abstract void checkAuthenticationAllowed(Authentication paramAuthentication)
throws AuthenticationException;
public abstract void registerSuccessfulAuthentication(Authentication paramAuthentication);
}
实现类:ConcurrentSessionControllerImpl
作为authenticationManager的sessionController属性的的concurrentSessionController,用以记录当前的session,——为什么不能用HttpSessionContextIntegrationFilter呢?
主要方法:
checkAuthenticationAllowed
concurrentSessionController里面真正用来记录session的其实是:SessionRegistry
public abstract interface SessionRegistry
{
public abstract Object[] getAllPrincipals();
public abstract SessionInformation[] getAllSessions(Object paramObject, boolean
paramBoolean);
public abstract SessionInformation getSessionInformation(String paramString);
public abstract void refreshLastRequest(String paramString);
public abstract void registerNewSession(String paramString, Object paramObject)
throws SessionAlreadyUsedException;
public abstract void removeSessionInformation(String paramString);
}
由SessionRegistryImpl实现
UserDetailsService接口
public abstract interface UserDetailsService
{
public abstract UserDetails loadUserByUsername(String paramString)
throws UsernameNotFoundException, DataAccessException;
}
PlaintextPasswordEncoder继承BasePasswordEncoder,用来加密password
主要方法
mergePasswordAndSalt
SaltSource是一个简单的接口,但是名字奇怪!盐???
public abstract interface SaltSource
{
public abstract Object getSalt(UserDetails paramUserDetails);
}
AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider
主要方法authenticate
ProviderManager extends AbstractAuthenticationManager
implements AuthenticationManager
主要方法authenticate,而authenticate又调用自身的doAuthenticate
destroy
最后,当整个应用关闭了,是调用FilterChainProxy的destroy,它获取所有filter,分别调用各自的destroy,