验证身份的对象元素
在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
认证流程
securiyManager是验证开始的地方,但从数据源取数据并作比较的工作是由Realm来进行的
ModularRealmAuthenticator:
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> 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,使用密码加密的方式提高安全性
<!--EndFragment-->