关于shiro就不用做过多介绍了,今天主要分析下登录过程
首先我大致画了个流程图(可能不够详细):
第一步:用户登录,根据用户登录名密码生产Token
-
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
-
Subject subject = SecurityUtils.getSubject();
-
subject.login(token);
-
public void login(AuthenticationToken token) throws AuthenticationException {
-
clearRunAsIdentitiesInternal();
-
Subject subject = securityManager.login( this, token);
-
-
PrincipalCollection principals;
-
-
String host = null;
-
-
if (subject instanceof DelegatingSubject) {
-
DelegatingSubject delegating = (DelegatingSubject) subject;
-
//we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
-
principals = delegating.principals;
-
host = delegating.host;
-
} else {
-
principals = subject.getPrincipals();
-
}
-
-
if (principals == null || principals.isEmpty()) {
-
String msg = "Principals returned from securityManager.login( token ) returned a null or " +
-
"empty value. This value must be non null and populated with one or more elements.";
-
throw new IllegalStateException(msg);
-
}
-
this.principals = principals;
-
this.authenticated = true;
-
if (token instanceof HostAuthenticationToken) {
-
host = ((HostAuthenticationToken) token).getHost();
-
}
-
if (host != null) {
-
this.host = host;
-
}
-
Session session = subject.getSession( false);
-
if (session != null) {
-
this.session = decorate(session);
-
} else {
-
this.session = null;
-
}
-
}
第二步:调用securityManager的login方法
-
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
-
AuthenticationInfo info;
-
try {
-
info = authenticate(token);
-
} catch (AuthenticationException ae) {
-
try {
-
onFailedLogin(token, ae, subject);
-
} catch (Exception e) {
-
if (log.isInfoEnabled()) {
-
log.info( "onFailedLogin method threw an " +
-
"exception. Logging and propagating original AuthenticationException.", e);
-
}
-
}
-
throw ae; //propagate
-
}
-
-
Subject loggedIn = createSubject(token, info, subject);
-
-
onSuccessfulLogin(token, info, loggedIn);
-
-
return loggedIn;
-
}
-
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
-
return this.authenticator.authenticate(token);
-
}
-
public AuthenticatingSecurityManager() {
-
super();
-
this.authenticator = new ModularRealmAuthenticator();
-
}
第四步:调用AbstractAuthenticator的authenticate方法
-
public final AuthenticationInfo authenticate(AuthenticationToken token) 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);
-
if (info == null) {
-
String msg = "No account information found for authentication token [" + token + "] by this " +
-
"Authenticator instance. Please check that it is configured correctly.";
-
throw new AuthenticationException(msg);
-
}
-
} catch (Throwable t) {
-
AuthenticationException ae = null;
-
if (t instanceof AuthenticationException) {
-
ae = (AuthenticationException) t;
-
}
-
if (ae == null) {
-
//Exception thrown was not an expected AuthenticationException. Therefore it is probably a little more
-
//severe or unexpected. So, wrap in an AuthenticationException, log to warn, and propagate:
-
String msg = "Authentication failed for token submission [" + token + "]. Possible unexpected " +
-
"error? (Typical or expected login exceptions should extend from AuthenticationException).";
-
ae = new AuthenticationException(msg, t);
-
}
-
try {
-
notifyFailure(token, ae);
-
} catch (Throwable t2) {
-
if (log.isWarnEnabled()) {
-
String msg = "Unable to send notification for failed authentication attempt - listener error?. " +
-
"Please check your AuthenticationListener implementation(s). Logging sending exception " +
-
"and propagating original AuthenticationException instead...";
-
log.warn(msg, t2);
-
}
-
}
-
-
-
throw ae;
-
}
-
-
log.debug( "Authentication successful for token [{}]. Returned account [{}]", token, info);
-
-
notifySuccess(token, info);
-
-
return info;
-
}
第五步:调用ModularRealmAuthenticator的doAuthenticate方法
-
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
-
assertRealmsConfigured();
-
Collection<Realm> realms = getRealms();
-
if (realms.size() == 1) {
-
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
-
} else {
-
return doMultiRealmAuthentication(realms, authenticationToken);
-
}
-
}
一个property 为realm的属性 对属性注入的时候调用的setRealm方法
-
public void setRealm(Realm realm) {
-
if (realm == null) {
-
throw new IllegalArgumentException( "Realm argument cannot be null");
-
}
-
Collection<Realm> realms = new ArrayList<Realm>( 1);
-
realms.add(realm);
-
setRealms(realms);
-
}
第六步:调用ModularRealmAuthenticator的doSingleRealmAuthentication方法
-
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
-
if (!realm.supports(token)) {
-
String msg = "Realm [" + realm + "] does not support authentication token [" +
-
token + "]. Please ensure that the appropriate Realm implementation is " +
-
"configured correctly or that the realm accepts AuthenticationTokens of this type.";
-
throw new UnsupportedTokenException(msg);
-
}
-
AuthenticationInfo info = realm.getAuthenticationInfo(token);
-
if (info == null) {
-
String msg = "Realm [" + realm + "] was unable to find account data for the " +
-
"submitted AuthenticationToken [" + token + "].";
-
throw new UnknownAccountException(msg);
-
}
-
return info;
-
}
第七步:调用AuthenticatingRealm的getAuthenticationInfo方法
-
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
-
-
AuthenticationInfo info = getCachedAuthenticationInfo(token);
-
if (info == null) {
-
//otherwise not cached, perform the lookup:
-
info = doGetAuthenticationInfo(token);
-
log.debug( "Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
-
if (token != null && info != null) {
-
cacheAuthenticationInfoIfPossible(token, info);
-
}
-
} else {
-
log.debug( "Using cached authentication info [{}] to perform credentials matching.", info);
-
}
-
-
if (info != null) {
-
assertCredentialsMatch(token, info);
-
} else {
-
log.debug( "No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
-
}
-
-
return info;
-
}
而我们的cacheManager哪来的呢,我们发现在setRealm方法中调用了setRealms
-
public void setRealms(Collection<Realm> 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.");
-
}
-
this.realms = realms;
-
afterRealmsSet();
-
}
-
-
protected void afterRealmsSet() {
-
applyCacheManagerToRealms();
-
applyEventBusToRealms();
-
}
到realms中,而这种方法代码为:
-
protected void applyCacheManagerToRealms() {
-
CacheManager cacheManager = getCacheManager();
-
Collection<Realm> realms = getRealms();
-
if (cacheManager != null && realms != null && !realms.isEmpty()) {
-
for (Realm realm : realms) {
-
if (realm instanceof CacheManagerAware) {
-
((CacheManagerAware) realm).setCacheManager(cacheManager);
-
}
-
}
-
}
-
}
(有点啰嗦,哈哈,自己当时就是这么想的)
在上面getAuthenticationInfo方法中,我们刚才说过第一行是从缓存中取AuthenticationInfo,如果为空
第八步:调用realm的doGetAuthenticationInfo方法
-
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
-
// TODO Auto-generated method stub
-
String userName = (String) token.getPrincipal();
-
//通过token获取用户信息,这里我们一般从数据库中查询
-
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, password, getName());
-
return authenticationInfo;
-
}
-
if (token != null && info != null) {
-
cacheAuthenticationInfoIfPossible(token, info);
-
}
关于从缓存中查询AuthenticationInfo以及缓存AuthenticationInfo信息的方法 这里就不作分析了,可以看做对一个map的操作吧
当然到这里还没完,同样在上面方法中,
-
if (info != null) {
-
assertCredentialsMatch(token, info);
-
} else {
-
log.debug( "No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
-
}
第九步:调用assertCredentialsMatch方法
-
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
-
CredentialsMatcher cm = getCredentialsMatcher();
-
if (cm != null) {
-
if (!cm.doCredentialsMatch(token, info)) {
-
//not successful - throw an exception to indicate this:
-
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
-
throw new IncorrectCredentialsException(msg);
-
}
-
} else {
-
throw new AuthenticationException( "A CredentialsMatcher must be configured in order to verify " +
-
"credentials during authentication. If you do not wish for credentials to be examined, you " +
-
"can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
-
}
-
}
第十步:调用CredentialsMatcher的doCredentialsMatch方法,当然CredentialsMatcher我们可以自定义了
第十一步:上面步骤都通过以后回到DefualtSecurityManager的login方法中
Subject loggedIn = createSubject(token, info, subject);
创建Subject
-
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
-
SubjectContext context = createSubjectContext();
-
context.setAuthenticated( true);
-
context.setAuthenticationToken(token);
-
context.setAuthenticationInfo(info);
-
if (existing != null) {
-
context.setSubject(existing);
-
}
-
return createSubject(context);
-
}