zoukankan      html  css  js  c++  java
  • Shiro

    之前在Authentication和Authorization中也提到Realm。

    无论是身份验证还是权限验证,无论数据以什么方式存在,我们都需要访问一些数据并将其转换为Shiro可以识别的格式。

    通常一个数据源对应一个Realm。因此,实现一个Realm时会用到该数据源相关的API。

    通常一个数据源中会同时保存身份相关数据与权限相关数据。因此,一个Realm实现类可以进行认证和授权两种操作。

    可以将Realm简单地理解为DAO。

    (虽然IDEA生成的type hirarchy diagram很漂亮,但是太大了...还是用回eclipse截图..)

    如果使用.ini配置,我们可以在[main]部分定义N个Realm,但我们可以用显式(explicit)和隐式(implicit)两种方式为securityManager指定Realm(有点IOC容器的意思)。

    显示指定就是常见的方式,即定义Realm后再为securityManager按需要的顺序指定Realm。

    fooRealm = com.company.foo.Realm
    barRealm = com.company.another.Realm
    bazRealm = com.company.baz.Realm
    
    securityManager.realms = $fooRealm, $barRealm, $bazRealm
    


    显示指定的方式相对较为明确,即使不改变Realm的定义,我们仍可以让验证和授权按我们给securityManager指定的顺序的Realm来执行。

    如果因为某些原因(可能是定义的Realm太多?)不想为securityManager.realms指定,我们也可以使用隐式方式。

    也就是说,把上面的配置改成如下形式就是隐式方式了:

    blahRealm = com.company.blah.Realm
    fooRealm = com.company.foo.Realm
    barRealm = com.company.another.Realm
    

    隐式方式其实就是不指定,只定义(define),Shiro会搜索配置中所有的Realm并将它们一一指定给securityManager。

    使用隐式方式时只要稍微改一下Realm的定义,Shiro就可能会给我们来个惊喜。

    在介绍Authentication的文章中,说的是当一个验证请求出现时Shiro框架的工作流程。

    在这里具体记录一下Realm负责的工作(虽然说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());
            }
    
            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 {
                        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。

    通常,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 {
    
        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;
    }
    

    如果可以从缓存中获得验证信息,下一步则检查密码是否匹配,即assertCredentialsMatch(token, info)。

    如果缓存中不存在验证信息则调用以下方法。

    protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
    

    这里我们暂时先不考虑缓存的情况,考虑doGetAuthenticationInfo应该做什么。

    有些人(比如我)直接在该方法中完成也验证,验证通过时返回SimpleAuthenticationInfo实例,失败则抛出相应的验证异常。

    但下面有个assertCredentialsMatch,说明doGetAuthenticationInfo本没有打算这样用,这种使用方式会让CredentialMatcher失去意义。

    参考JdbcRealm的实现,只是根据身份(用户名)去查询并返回SimpleAuthenticationInfo实例。

    然后让assertCredentialsMatch比较token和authenticationInfo。

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

    既然提到了CredentialMatcher,我们来看看他的意义所在。 首先说明,AuthenticatingRealm的默认CredentialMatcher是...

    public AuthenticatingRealm() {
        this(null, new SimpleCredentialsMatcher());
    }
    


    如果仅仅是做密码字符比较我们大可不必做出这样一个接口(字符串比较的可插拔+可定制么?)

    之前在说Authentication的时候就提过,AuthenticationToken的Principal和Credential可以是任何类型的,光是拿过来直接比较是否相符也不只是比较密码字符那么简单了。

    SimpleCredentialsMatcher就是用来比较两个credential是否相同的。

    其doCredentialsMatch方法返回其equals方法的返回值。

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

    当然,实现类不只是SimpleCredentialsMatcher...

    SimpleCredentialsMatcher下还跟着HashedCredentialsMatcher,再往下就都deprecated了。

    说到HashedCredentialsMatcher,他只是给密码加个salt以提高安全。

    其中hashSalted属性基本不用考虑,因为从Shiro 1.1开始salt是根据SaltedAuthenticationInfo的getCredentialSalt()方法返回的non-null value。

    public HashedCredentialsMatcher() {
        this.hashAlgorithm = null;
        this.hashSalted = false;
        this.hashIterations = 1;
        this.storedCredentialsHexEncoded = true; //false means Base64-encoded
    }
    

    说到salting就不得不说SaltedAuthenticationInfo,该接口继承AuthenticationInfo,即除了principal和credential,他还有一个credentialsSalt。

    public interface SaltedAuthenticationInfo extends AuthenticationInfo {
    
        /**
         * Returns the salt used to salt the account's credentials or {@code null} if no salt was used.
         *
         * @return the salt used to salt the account's credentials or {@code null} if no salt was used.
         */
        ByteSource getCredentialsSalt();
    }
    

    HashedCredentialsMatcher有个默认的salt,是将自己的principal作为salt,后来这个方法也被deprecated了。

    因为从1.1开始,Shiro禁止salt从用户的登录信息中获取,而应该从数据源获取。

    @Deprecated
    protected Object getSalt(AuthenticationToken token) {
        return token.getPrincipal();
    }
    

    这一系列方法和属性会从Shiro 2.0开始彻底消失。

  • 相关阅读:
    JQuery学习四(过滤选择器)
    JQuery学习三(隐式迭代和节点遍历)
    JQuery学习二(获取元素控件并控制)
    JQuery学习一
    Dom中select练习
    DOM动态操纵控件案例
    DOM学习控件定位和案例
    DOM案例五星评分控件
    DOM动态增加控件
    DOM用TagName操作标签
  • 原文地址:https://www.cnblogs.com/kavlez/p/4133601.html
Copyright © 2011-2022 走看看