前言:本文大部分是原创,自己一点点看源码抠出来的,都是自己的理解,技术有限,有不对的地方还请大家指正。文中引用了大量的源码,自己加的注释,可以从头看出整个的验证过程,方便在各个地方自定义扩展,类关系图不画了,大家凑合看~
正文:
public interface SecurityManager
extends Authenticator, Authorizer, SessionManage
SecurityManager的默认实现类DefaultSecurityManager,继承了SessionsSecurityManager,继承了AuthorizingSecurityManager,继承了AuthenticatingSecurityManager,继承了RealmSecurityManager,继承了CachingSecurityManager,实现了implements SecurityManager,Destroyable, CacheManagerAware
再来看newDefaultSecurityManager();做的事情
1. public DefaultSecurityManager() {
2. super();
3. this.subjectFactory = new DefaultSubjectFactory();
4. this.subjectDAO = new DefaultSubjectDAO();
5. }
这里暂时不深究每个构造器所做的具体事情。有兴趣的同学,可一直观察DefaultSecurityManager的所有父类构造器的处理逻辑。
类名称 | 构造方法创建的默认对象 |
DefaultSecurityManager | DefaultSubjectFactory,DefaultSubjectDAO |
SessionsSecurityManager | DefaultSessionManager |
AuthorizingSecurityManager | ModularRealmAuthorizer |
AuthenticatingSecurityManager | ModularRealmAuthenticator
举例: public AuthenticatingSecurityManager() { authenticator = new ModularRealmAuthenticator(); }
|
RealmSecurityManager | 无 |
CachingSecurityManager | 无 |
在上面的表格中已经清晰的描述了各个类所设置的默认对象,至于用途后面再讲解。需要注意的是,RealmSecurityManager、CachingSecurityManager并没有设置默认的对象,所以这个是交给开发人员自己配置的。
DefaultSecurityManager的Login:
public Subject login(Subject subject, AuthenticationTokentoken)
throws AuthenticationException
{
AuthenticationInfo info;
try
{
info =authenticate(token);//来自AuthenticatingSecurityManager
}
catch(AuthenticationException ae)
{
try
{
onFailedLogin(token, ae,subject);
}
catch(Exception e)
{
if(log.isInfoEnabled())
log.info("onFailedLogin method threw an exception. Logging and propagating originalAuthenticationException.", e);
}
throw ae;
}
Subject loggedIn = createSubject(token,info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
AuthenticatingSecurityManager的authenticate方法:
public AuthenticationInfo authenticate(AuthenticationTokentoken)
throws AuthenticationException
{
return authenticator.authenticate(token);
// authenticator是该类的一个属性,通过构造方法注入的。默认//ModularRealmAuthenticator
}
ModularRealmAuthenticator的authenticate方法(这其实是来自AbstractAuthenticator)
public final AuthenticationInfo authenticate(AuthenticationTokentoken)
throws AuthenticationException
{
if(token == null)
throw new IllegalArgumentException("Method argumet (authentication token) cannot be null.");
log.trace("Authentication attempt received for token [{}]", token);
AuthenticationInfo info;
try
{
info = doAuthenticate(token);//这个来自ModularRealmAuthenticator实现
if(info == null)
{
String msg = (newStringBuilder()).append("No accountinformation found for authentication token [").append(token).append("] by this ").append("Authenticator instance. Please check that it is configured correctly.").toString();
throw new AuthenticationException(msg);
}
}
catch(Throwable t)
{
AuthenticationException ae = null;
if(t instanceof AuthenticationException)
ae =(AuthenticationException)t;
if(ae == null)
{
String msg = (newStringBuilder()).append("Authenticationfailed for token submission [").append(token).append("]. Possibleunexpected ").append("error? (Typical or expected login exceptions shouldextend from AuthenticationException).").toString();
ae = new AuthenticationException(msg, t);
}
try
{
notifyFailure(token, ae);
}
catch(Throwable t2)
{
if(log.isWarnEnabled())
{
String msg = "Unable to send notification for failed authenticationattempt - listener error?. Please checkyour AuthenticationListener implementation(s). Logging sending exception and propagating originalAuthenticationException instead...";
log.warn(msg, t2);
}
}
throw ae;
}
log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info);
notifySuccess(token, info);
return info;
}
// ModularRealmAuthenticator实现AbstractAuthenticator中的抽象方法doAuthenticate)
protected AuthenticationInfo doAuthenticate(AuthenticationTokenauthenticationToken)
throws AuthenticationException
{
assertRealmsConfigured();
Collection realms = getRealms();
if(realms.size()== 1)
returndoSingleRealmAuthentication((Realm)realms.iterator().next(),authenticationToken);
else
return doMultiRealmAuthentication(realms,authenticationToken);
}
//在这里插入一段必要的解释
(ModularRealmAuthenticator的参数realms的注入问题:
在用spring给securityManager 注入参数的时候
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager"ref="cacheManager" />
<property name="realm"ref="userRealm" />
</bean>
注入参数realm或者注入realms的时候,都会调用RealmSecurityManager中相应的set方法注入值,在set方法中有一个方法:afterRealmsSet()需要特别注意;
public void setRealms(Collection realms)
{
if(realms == null)
throw new IllegalArgumentException("Realms collection argument cannot be null.");
if(realms.isEmpty())
{
throw new IllegalArgumentException("Realms collection argument cannot be empty.");
} else
{
this.realms = realms;
afterRealmsSet();//该方法实际上是AuthorizingSecurityManager的afterRealmsSet()方法.这是因为这是一个继承关系,所以这个方法调用的是最底层的子类的方法,然后初始化相应的类中的realms,具体看一下这个afterRealmsSet方法。
return;
}
}
AuthorizingSecurityManager的afterRealmsSet()方法:
protected void afterRealmsSet()
{
//调用父类AuthenticatingSecurityManager的afterRealmsSet()方法
super.afterRealmsSet();
初始化authorizer默认鉴权实现类ModularRealmAuthorizer中的Realms参数
if(authorizer instanceof ModularRealmAuthorizer)
((ModularRealmAuthorizer)authorizer).setRealms(getRealms());
}
AuthenticatingSecurityManager的afterRealmsSet()方法:
protected void afterRealmsSet()
{
//调用父类RealmSecurityManager的afterRealmsSet()方法
super.afterRealmsSet();
//初始化认证authenticator的默认实现类ModularRealmAuthenticator中的、、//realms参数
if(authenticator instanceofModularRealmAuthenticator)
((ModularRealmAuthenticator)authenticator).setRealms(getRealms());
}
)
继续:
protectedAuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationTokentoken)
{
if(!realm.supports(token))
{
String msg = (newStringBuilder()).append("Realm [").append(realm).append("] does not support authentication token [").append(token).append("]. Please ensurethat the appropriate Realm implementation is ").append("configured correctly or that the realm acceptsAuthenticationTokens of this type.").toString();
throw new UnsupportedTokenException(msg);
}
AuthenticationInfo info =realm.getAuthenticationInfo(token);
//这个realm是从注入的realms中取得(即Myrealm,关于注入的过程上面已经解释过);
if(info == null)
{
String msg = (newStringBuilder()).append("Realm [").append(realm).append("] was unable to find account data for the ").append("submitted AuthenticationToken [").append(token).append("].").toString();
throw new UnknownAccountException(msg);
} else
{
return info;
}
}
Myrealm继承AuthorizingRealm继承AuthenticatingRealm(里面的getAuthenticationInfo这个方法来验证身份)
public final AuthenticationInfogetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException
{
AuthenticationInfo info =getCachedAuthenticationInfo(token);
if(info == null)
{
info =doGetAuthenticationInfo(token);//先在这验证一次(这个方法一定都很熟悉了)
log.debug("Looked up AuthenticationInfo [{}] fromdoGetAuthenticationInfo", info);
if(token != null && info != null)
cacheAuthenticationInfoIfPossible(token, info);
} else
{
log.debug("Using cached authentication info [{}] to performcredentials matching.",info);
}
if(info != null)
assertCredentialsMatch(token,info);//再验证一次(进行密码匹配)
else
log.debug("No AuthenticationInfo found for submittedAuthenticationToken [{}]. Returningnull.", token);
return info;
}
其中的assertCredentialsMatch方法:还是来自AuthenticatingRealm
protected void assertCredentialsMatch(AuthenticationToken token,AuthenticationInfo info)
throws AuthenticationException
{
CredentialsMatcher cm =getCredentialsMatcher();
if(cm != null)
{
if(!cm.doCredentialsMatch(token,info))
{
String msg = (newStringBuilder()).append("Submittedcredentials for token [").append(token).append("] did not match the expected credentials.").toString();
throw new IncorrectCredentialsException(msg);
} else
{
return;
}
} else
{
throw new AuthenticationException((new StringBuilder()).append("A CredentialsMatcher must be configured in order toverify credentials during authentication. If you do not wish for credentials to be examined, you can configure an ").append(org/apache/shiro/authc/credential/AllowAllCredentialsMatcher.getName()).append(" instance.").toString());
}
}
其中CredentialsMatchercm = getCredentialsMatcher();是一个AuthenticatingRealm的一个属性credentialsMatcher,通过sprnig属性注入注入的
<bean id="userRealm" class="com.xhrd.platform.shiro.realm.UserRealm">
<property name="userService"ref="userService" />
<property name="credentialsMatcher"ref="credentialsMatcher" />
<property name="cachingEnabled"value="true" />
<property name="authenticationCachingEnabled"value="true" />
<property name="authenticationCacheName"value="authenticationCache" />
<property name="authorizationCachingEnabled"value="true" />
<property name="authorizationCacheName"value="authorizationCache" />
</bean>
CredentialsMatcher cm中的doCredentialsMatch方法可以自己实现验证逻辑
1. public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
2. String username = (String)token.getPrincipal();
3. //retry count + 1
4. Element element = passwordRetryCache.get(username);
5. if(element == null) {
6. element = new Element(username , new AtomicInteger(0));
7. passwordRetryCache.put(element);
8. }
9. AtomicInteger retryCount = (AtomicInteger)element.getObjectValue();
10. if(retryCount.incrementAndGet() > 5) {
11. //if retry count > 5 throw
12. throw new ExcessiveAttemptsException();
13. }
14.
15. boolean matches = super.doCredentialsMatch(token, info);
16. if(matches) {
17. //clear retry count
18. passwordRetryCache.remove(username);
19. }
20. return matches;
21. }
关于密码匹配这部分看看这篇博客(http://lgbolgger.iteye.com/blog/2168520,感谢博主)