前两篇文章简单的分析了一下shiro的认证过程和授权过程. 并没有太深入源码, 这次分析一下从用户登陆的认证过程以及登陆成功后的权限判断过程.
准备工作
一个用来接收username和password, 执行login方法的Controller.
StudentRealm和TeacherRealm, 我令他们都能通过认证.
(1) 前面的过程不多说了, 分别是在controller中调用WebDelegatingSubject的login(token) --> DefaultWeb
SecurityManager的login(this, token) --> DefaultWebSecurityManager的authenticate(token) --> ModularRealmAuthenticator的authenticate(token) --> ModularRealmAuthenticator的doAuthenticate(token)
这里贴出doAuthenticate的代码
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection realms = this.getRealms();
return realms.size() == 1?this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken):this.doMultiRealmAuthentication(realms, authenticationToken);
}
因为我配置了两个Realm,所以会调用
doMultiRealmAuthentication(realms, authenticationToken)方法, 我们就从这个方法正式开始分析
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
AuthenticationStrategy strategy = this.getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
if(log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", Integer.valueOf(realms.size()));
}
Iterator var5 = realms.iterator();
while(var5.hasNext()) {
Realm realm = (Realm)var5.next();
aggregate = strategy.beforeAttempt(realm, token, aggregate);
if(realm.supports(token)) {
log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
AuthenticationInfo info = null;
Throwable t = null;
try {
info = realm.getAuthenticationInfo(token);
} catch (Throwable var11) {
t = var11;
if(log.isWarnEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.warn(msg, var11);
}
}
aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
首先获取认证策略, 我是用的是默认策略(AtLeastOneSuccessfulStrategy)
执行strategy的beforeAllAttempts后会创建一个SimpleAuthenticationInfo,名为aggregate.
(3)获取Realm集合的Iterator对象后我们执行第一次循环: 刚开始会执行strategy.beforeAttempt(realm, token, aggregate), 在此策略下会将aggregate原样返回
(4)执行每个realm的getAuthenticationInfo(token),获取认证信息, 这句代码写在try-catch块中, 如果认证不通过(如密码错误)则会抛出异常, info为空. (本例中两个Realm都能通过认证)
-------------------------------------------------------------------------------------------------------------------------------
在这里详细解释一下info在Realm中是如何构建出来的:
4.1) 前文已经介绍过, 在Reaml中返回的是一个SimpleAuthenticationInfo类的实例, 这里我就从它的构造方法来分析一下这个类的作用.
4.2) 注意principal参数, 前文我们为了简单起见, 将principal简单的当做了一个String类型的用户名. 而它在构造方法中是Object类型的, 也就是说这个principal不仅仅可以是用户名, 更可以是一个对象,一个集合(为什么可以是集合,后面会解释).
构造方法第一行创建了一个SimplePrincipalCollection对象并赋给
SimpleAuthenticationInfo类的principals属性. 现在我们来看看SimplePrincipalCollection的这个构造方法.
public SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) {
this.principals = new SimplePrincipalCollection(principal, realmName);
this.credentials = hashedCredentials;
this.credentialsSalt = credentialsSalt;
}
4.3) 可以看到这个构造方法中对principal的类型做了判断,如果是一个Collection,则调用该类中的addAll方法,并将principal强转与realmName一起传入. 如果不是,则执行该类的add方法,并将principal和realmName当做参数传入.
public SimplePrincipalCollection(Object principal, String realmName) {
if(principal instanceof Collection) {
this.addAll((Collection)principal, realmName);
} else {
this.add(principal, realmName);
}
4.4) 现在我们来看看这两个方法:
1.addAll方法:
public void addAll(Collection principals, String realmName) {
if(realmName == null) {
throw new IllegalArgumentException("realmName argument cannot be null.");
} else if(principals == null) {
throw new IllegalArgumentException("principals argument cannot be null.");
} else if(principals.isEmpty()) {
throw new IllegalArgumentException("principals argument cannot be an empty collection.");
} else {
this.cachedToString = null;
this.getPrincipalsLazy(realmName).addAll(principals);
}
}
做了对principals和realmName的非空判断后,将realmName传入getPrincipalsLazy方法
getPrincipalsLazy方法:
protected Collection getPrincipalsLazy(String realmName) {
if(this.realmPrincipals == null) {
this.realmPrincipals = new LinkedHashMap();
}
Object principals = (Set)this.realmPrincipals.get(realmName);
if(principals == null) {
principals = new LinkedHashSet();
this.realmPrincipals.put(realmName, principals);
}
return (Collection)principals;
}
这里解释一下realmPrincipals属性:
在类中是这么定义的: private Map<String, Set> realmPrincipals; key是realm的名字,Set中保存着在所对应key的realm中获取的身份信息.
在类中是这么定义的: private Map<String, Set> realmPrincipals; key是realm的名字,Set中保存着在所对应key的realm中获取的身份信息.
回到getPrincipalsLazy方法
* 首先判断realmPrincipals属性是否为空, 若为空则为其创建一个LinkedHashMap.
* 从该属性中获取传入的参数所对应的value, 并赋值给principals
* 若principals为空,表示该realmName在realmPrincipals属性中还没有对应的value, 则为其创建一个LinkedHashSet, 并将该realmName和principals放入realmPrincipals属性.
* 强转principals为Collection后将其返回.
* 从该属性中获取传入的参数所对应的value, 并赋值给principals
* 若principals为空,表示该realmName在realmPrincipals属性中还没有对应的value, 则为其创建一个LinkedHashSet, 并将该realmName和principals放入realmPrincipals属性.
* 强转principals为Collection后将其返回.
回到addAll方法, 调用getPrincipalsLazy方法返回值的addAll方法,参数为构造方法的参数principals, 意为将principals中的所有元素添加到在getPrincipalsLazy方法
中创建的principals中, 也就是将所有身份信息添加到realmPrincipals中, 对应的key就是realmName.
中创建的principals中, 也就是将所有身份信息添加到realmPrincipals中, 对应的key就是realmName.
2.add方法:
public void add(Object principal, String realmName) {
if(realmName == null) {
throw new IllegalArgumentException("realmName argument cannot be null.");
} else if(principal == null) {
throw new IllegalArgumentException("principal argument cannot be null.");
} else {
this.cachedToString = null;
this.getPrincipalsLazy(realmName).add(principal);
}
}
大体流程与addAll方法一致,只不过最后调用的是getPrincipalsLazy方法返回值的add方法,(因为该add方法的参数principal是"一个"元素)
4.5) 完成了对SimplePrincipalsCollection对象的创建. 然后接着执行SimpleAuthenticationInfo构造方法的后两句, 将从数据库获取的密码和salt值赋给它对应的属性. 完成了
SimpleAuthenticationInfo对象的创建.
4.6) 所以info信息中包含了创建它的Realm的realmName, 从数据库中获取的密码, 以及salt值.
-------------------------------------------------------------------------------------------------------------------------------
5)当第一个realm的info成功返回后,会执行aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);此方法会判断传入的info和aggregate是否为空,如果都为空,返回aggregate;若两者相对, 则返回不为空的一方;如果都不为空,则执行strategy的merge(singleRealmInfo, aggregateInfo)方法, (merge:融合) . 这里会执行merge方法
public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {
AuthenticationInfo info;
if(singleRealmInfo == null) {
info = aggregateInfo;
} else if(aggregateInfo == null) {
info = singleRealmInfo;
} else {
info = this.merge(singleRealmInfo, aggregateInfo);
}
return info;
}
6)merge方法会调用aggregate的merge方法,并将info传入.
protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) {
if(aggregate instanceof MergableAuthenticationInfo) {
((MergableAuthenticationInfo)aggregate).merge(info);
return aggregate;
} else {
throw new IllegalArgumentException("Attempt to merge authentication info from multiple realms, but aggregate AuthenticationInfo is not of type MergableAuthenticationInfo.");
}
}
7) 该方法先对info进行非空校验后,先判断aggregate的principals是否为空,如果为空,将info的principals赋值给它;如果不为空,调用aggregate的addAll(info),因为是第一次执行,会将info的principals赋值给aggregate的principals;判断aggregate的credentialsSalt是否为空,做法同上, 所以将info的credentialsSalt赋值给它.再同上,将info的Credentials赋值给它, 完成"融合", 方法结束,将aggregate返回.
public void merge(AuthenticationInfo info) {
if(info != null && info.getPrincipals() != null && !info.getPrincipals().isEmpty()) {
if(this.principals == null) {
this.principals = info.getPrincipals();
} else {
if(!(this.principals instanceof MutablePrincipalCollection)) {
this.principals = new SimplePrincipalCollection(this.principals);
}
((MutablePrincipalCollection)this.principals).addAll(info.getPrincipals());
}
if(this.credentialsSalt == null && info instanceof SaltedAuthenticationInfo) {
this.credentialsSalt = ((SaltedAuthenticationInfo)info).getCredentialsSalt();
}
Object thisCredentials = this.getCredentials();
Object otherCredentials = info.getCredentials();
if(otherCredentials != null) {
if(thisCredentials == null) {
this.credentials = otherCredentials;
} else {
if(!(thisCredentials instanceof Collection)) {
HashSet credentialCollection = new HashSet();
credentialCollection.add(thisCredentials);
this.setCredentials(credentialCollection);
}
Collection credentialCollection1 = (Collection)this.getCredentials();
if(otherCredentials instanceof Collection) {
credentialCollection1.addAll((Collection)otherCredentials);
} else {
credentialCollection1.add(otherCredentials);
}
}
}
}
}
8) 进行第二次循环, 唯一不同的是此时aggregate已经不是新创建的对象,它包含了上一个info的信息. 再获取了第二个realm的info后,继续执行aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
此时因为aggregate和info都不为空,所以会执行strategy.merge(aggregate,info)
此时因为aggregate和info都不为空,所以会执行strategy.merge(aggregate,info)
9) 同样,会调用aggregate的merge方法,传入info
10) 进入merge方法,此时aggregate的principals已经不为空了,会调用aggregate的principals的addAll方法, 将info的principals传入.
11)对该info的principals的realmName遍历,取出每个realmName对应的principal,再遍历principal,将每个principal都添加到aggregate的principals中. 该方法执行完后,aggregate的principals中已经有两个元素了,分别是StudentRealm的realmName和对应的principal, 以及TeacherRealm的realmName和对应的principal.
12) 返回到aggregate的merge方法, 判断credentialsSalt是否为空, 此时已经不为空所以不执行if下的语句. 再获取Credentials,此时aggregate的credentials不为空, 所以执行else下语句,先判断info的credentials是否为一个Collection, 显然不是,所以创建了一个名为credentialCollection的HashSet,看名字可以看得出来是存放credentials的,将info的credential添加到该集合后set给aggregate对象本身的credentials(该属性为Object类型);最后再根据aggregate的credentials类型,添加到credentialCollection中去.方法执行完毕.
13)返回到ModularRealmAuthenticator的doMultiRealmAuthentication方法,此时aggregate中的principals属性包含了两个realm的名字和对应的权限信息.credentials属性包含了两个realm中返回的credentials(密码).
14) 执行strategy的afterAllAttempts方法, 将aggregate原样返回. 退出doMultiRealmAuthentication方法.
15) 原路返回到DefaultSecurityManager的login方法中,执行DefaultSecurityManager的createSubject(token, info, subject)方法. 这个方法就是将后台获取的所有信息都保存在subject中去, 可供subject直接调用,来完成授权等操作.
16) 此时若访问需要授权的页面, 则会调用Realm中的doGetAuthorizationInfo(PrincipalCollection principalCollection),
这里的principalCollection其实就是保存在subject中的
aggregate.然后就可以在
doGetAuthorizationInfo中从principalCollection中获取身份信息, 从数据库中再获取授权信息后返回AuthorizationInfo即可完成授权操作.