身份认证流程及原理

验证身份的对象元素

在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,使用密码加密的方式提高安全性

 

你可能感兴趣的:(shiro)