验证身份的对象元素
在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
认证流程
securiyManager是验证开始的地方,但从数据源取数据并作比较的工作是由Realm来进行的
ModularRealmAuthenticator:
protected AuthenticationInfo doMultiRealmAuthentication(Collection realms, AuthenticationToken token) {
//获取认证策略
AuthenticationStrategy strategy = getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
}
//循环realm
for (Realm realm : realms) {
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 {
//关键:调用realm的getAuthenticationInfo进行认证,token为用户的token
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
t = throwable;
if (log.isDebugEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.debug(msg, t);
}
}
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;
}
在Realm开始处理验证的逻辑之前,Authenticator将调用Realm的 supports 方法去验证当前Realm是否支持获得的AuthenticationToken。
boolean supports (AuthenticationToken token);
通常,Realm检查的是token的类型,比如在 AuthenticatingRealm 中检查类型是否相同。
public boolean supports(AuthenticationToken token) {
return token != null && getAuthenticationTokenClass().isAssignableFrom(token.getClass());
}
另外,AuthenticatingRealm的constructor中类型默认为
authenticationTokenClass = UsernamePasswordToken .class;
如果当前Realm支持提交过来的token,authenticator则调用 getAuthenticationInfo (token) 方法。
以 AuthenticatingRealm 为例(注意是final):
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//先从缓存中取
//token为需验证的用户信息(前台传来的需要验证的用户)
//info为根据需验证的用户信息(前台传来的需要验证的用户)获得校验的用户验证信息(数据源中获得的正确的用户信息)
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
//自定义Realm的扩展点
//根据需要验证的用户信息获得正确的用户信息(获得方式:数据库,配置文件等)
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) {
//真正的验证,token,info
//此验证内,如果验证不对,则抛出异常
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
有些人直接在doGetAuthenticationInfo该方法中完成也验证,验证通过时返回SimpleAuthenticationInfo实例,失败则抛出相应的验证异常。
但下面有个 assertCredentialsMatch ,说明doGetAuthenticationInfo本没有打算这样用,这种使用方式会让 CredentialMatcher 失去意义。
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
//获得CredentialsMatcher,有多种实现
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.");
}
}
AuthenticatingRealm的默认CredentialMatcher是SimpleCredentialsMatcher
public AuthenticatingRealm() {
this(null, new SimpleCredentialsMatcher());
}
protected boolean equals(Object tokenCredentials, Object accountCredentials) {
if (log.isDebugEnabled()) {
log.debug("Performing credentials equality check for tokenCredentials of type [" +
tokenCredentials.getClass().getName() + " and accountCredentials of type [" +
accountCredentials.getClass().getName() + "]");
}
if (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) {
if (log.isDebugEnabled()) {
log.debug("Both credentials arguments can be easily converted to byte arrays. Performing " +
"array equals comparison");
}
byte[] tokenBytes = toBytes(tokenCredentials);
byte[] accountBytes = toBytes(accountCredentials);
return Arrays.equals(tokenBytes, accountBytes);
} else {
return accountCredentials.equals(tokenCredentials);
}
}
当然,实现类还有HashedCredentialsMatcher,使用密码加密的方式提高安全性