您的位置:首页 > 其它

Shiro登录认证框架源码解析

2017-02-19 13:58 423 查看
shiro是apahce的开源安全框架,因为其强大、灵活的特点,则we在应用中经常使用,shiro的认证和授权过程是分开的,下面从shiro的源码解析shiro的认证过程

subject.login(token);

DelegatingSubject

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.sessi     }
}


然后调用DefaultSecurityManager的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;
}


接着调用 父类的AuthenticatingSecurityManager处理

public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}


接着调用安全管理器内部的认真器处理:AbstractAuthenticator

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

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);
}
}


然后接着调用:

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:

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;
}


分析: info = doGetAuthenticationInfo(token); 获取客户自定义Realm的获取的认证信息

assertCredentialsMatch(token, info);

这个是实际进行客户登陆信息和Realm数据源进行比较的方法,根据该方法返回的接口进行整体意义的客户登陆信息认证

AuthenticatingRealm:assertCredentialsMatch(token, info)方法:

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 cm = getCredentialsMatcher();

这里是获取用户自定义的密钥匹配器的方法,可以在Spring中定义的init的方法,在Realm进行密钥匹配器初始化的自定义方法:

HashedCredentialsMatcher  credentialsMatcher = new HashedCredentialsMatcher();
//设置加密的方式
credentialsMatcher.setHashAlgorithmName("MD5");
//生成hash的迭代次数
credentialsMatcher.setHashIterations(1024);
//设置凭证的匹配方式
setCredentialsMatcher(credentialsMatcher);


cm.doCredentialsMatch(token, info)代码分析:

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = getCredentials(token);
Object accountCredentials = getCredentials(info);
return equals(tokenCredentials, accountCredentials);
}


这个代码是最底层的登陆认证信息和Realm数据源进行比较的实现,通过自定义/默认的密钥匹配器比较,如果一致返回true,表示登陆认证通过,如果返回false,表示认证失败

上述代码的流程就是shiro完成整个认证的详细过程,希望对想了解shiro的底层工作原理的同学有点启发
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息