吃透Shiro源码5----AuthenticationRealm

技术手法

(1)AuthenticationRealm设计思路

AuthenticationRealm这个类我看了好久,实际上最最核心的方法就是提供:如何通过用户传递的AuthenticationToken来获取AuthentioncationInfo的方法。

@Override
    public final AuthenticationInfo 
    getAuthenticationInfo(AuthenticationToken token) 
                           throws AuthenticationException {

        AuthenticationInfo info = 
                  getCachedAuthenticationInfo(token);
        if (info == null) {
            //核心:让子类通过token获取到info,这也是要强制重写的方法
            info = doGetAuthenticationInfo(token);
            //尝试缓存
            if (token != null && info != null) {
                LOGGER.debug("无缓存,尝试做token与info的缓存");
                //尝试缓存一下info
                cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            //说明有缓存
            LOGGER.debug("从缓存中获取Info信息:{}", info);
        }

        if (info != null) {
            LOGGER.debug("校验info与token");
            assertCredentialsMatch(token, info);
        } else {
            LOGGER.debug("通过token压根就没找到info");
        }
        //返回有info对象,说明已经校验通过
        return info;
    }

核心步骤:(1)getCachedAuthenticationInfo(token)。通过token获取认证缓存。如果有缓存,取缓存中数据。没有则返回空

    private AuthenticationInfo 
      getCachedAuthenticationInfo(AuthenticationToken token) {
        AuthenticationInfo info = null;

        Cache<Object, AuthenticationInfo> cache = 
                               this.authenticationCache;
        if (cache != null && token != null) {
            LOGGER.debug("尝试获取AuthenticationInfo缓存");
            //token.getPrincipal()
            Object key = getAuthenticationCacheKey(token);
            info = cache.get(key);
            if (info == null) {
                LOGGER.info("暂无认证缓存");
            } else {
                LOGGER.info("为:{}找到AuthenticationInfo缓存", key);
            }
        }
        return info;
    }

核心步骤(2):没有取到缓存。那么就让用户去调用 doGetAuthenticationInfo(AuthenticationToken token)方法去获取对应的缓存。这也是为什么我们要重写此方法的目的。等拿到了Token,也拿到了Info,那么就尝试把Info加入缓存中。因此调用了cacheAuthenticationInfoIfPossible(token,info)方法。

 private void cacheAuthenticationInfoIfPossible
        (AuthenticationToken token, AuthenticationInfo info) {
        if (!isAuthenticationCachingEnabled()) {
            LOGGER.debug("未开启认证缓存");
            return;
        }
        //获取可用的缓存
        Cache<Object, AuthenticationInfo> cache = 
                     getAvailableAuthenticationCache();
        if (cache != null) {
            //如果缓存不为空,说明有CacheManager并创建了Cache,
            //也把Cache放入了CacheManager中
            Object key = getAuthenticationCacheKey(token);
            //将token.getPrincipal()与info缓存
            cache.put(key, info);
        }
    }

核心步骤(3):cacheAuthenticationInfoIfPossible(token,info)方法里调用了一个叫做getAvailableAuthenticationCache(token)的方法。这里尝试获取可用的认证缓存。如果开启了缓存,并且缓存为空,这里会由由缓存生命周期管理器CacheManager对象来创建一个指定name的Cache。

    private Cache<Object, AuthenticationInfo> 
                  getAvailableAuthenticationCache() {
        //获取内部缓存
        Cache<Object, AuthenticationInfo> cache = 
                              this.authenticationCache;
        //判断缓存认证缓存是否开启
        boolean authcCachingEnabled = 
                            isAuthenticationCachingEnabled();
        if (cache == null && authcCachingEnabled) {
            //如果缓存是空,并且开启了缓存,
            //使用懒加载方式加载一个缓存
            cache = getAuthenticationCacheLazy();
        }
        return cache;
    }

最后懒加载Cache对象。由CacheManager对象创建Cache,并最终返回到调用处,将新创建的Token.getPrincipal()与Info缓存。接下来就是校验一下用户输入的账号密码对不对的问题了。。

不得不说Shiro框架里异常创建的是真TM的多,什么异常情况都创建一个异常。

private Cache<Object, AuthenticationInfo> 
                          getAuthenticationCacheLazy() {
        if (this.authenticationCache == null) {
            LOGGER.debug("创建认证缓存");
            //拿到缓存声明周期管理对象,由CacheRealm提供
            CacheManager cacheManager = getCacheManager();
            if (cacheManager != null) {
                String cacheName = getAuthenticationCacheName();
                //创建一个缓存,没有此名字的缓存则创建一个
                this.authenticationCache = 
                         cacheManager.getCache(cacheName);
            }
        }
        //返回此缓存对象,到底缓存什么,
        //缓存Object(principal)对应的authenticationInfo
        return this.authenticationCache;
    }

重点研究源码

import com.wise.security.ShiroException;
import com.wise.security.authc.*;
import com.wise.security.authc.credential.CredentialsMatcher;
import com.wise.security.authc.credential.SimpleCredentialsMatcher;
import com.wise.security.cache.Cache;
import com.wise.security.cache.CacheManager;
import com.wise.security.subject.PrincipalCollection;
import com.wise.security.util.CollectionUtils;
import com.wise.security.util.Initializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Realm接口的顶级抽象实现,
 * 该接口仅实现身份验证支持(登录)操作,而将授权(访问控制)行为留给子类。
 * <p>
 * 缓存性质
 * 对于经常对同一帐户重复进行身份验证的应用程序
 * 启用身份验证缓存以减轻任何后端数据源的持续负载可能是谨慎的做法。
 */
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {

    private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticatingRealm.class);
    private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger();

    /**
     * 默认的认证缓存对象名称结尾
     */
    private static final String DEFAULT_AUTHEN_CACHE_SUFFIX = ".authenticationCache";

    /**
     * 校验Info与Token的对象
     */
    private CredentialsMatcher credentialsMatcher;

    /**
     * 认证缓存对象
     * Principal对应AuthenticationInfo
     */
    private Cache<Object, AuthenticationInfo> authenticationCache;

    /**
     * 是否开启认证缓存
     */
    private boolean authenticationCachingEnabled;
    private String authenticationCacheName;

    /**
     * 此Realm能够支持的用户输入的AuthenticationToken类型
     * {@link Realm#supports(AuthenticationToken)}
     */
    private Class<? extends AuthenticationToken> authenticationTokenClass;

    //------------------------------------构造函数  Start-------------------------------------------

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

    public AuthenticatingRealm(CredentialsMatcher credentialsMatcher) {
        this(null, credentialsMatcher);
    }

    public AuthenticatingRealm(CacheManager cacheManager) {
        this(cacheManager, new SimpleCredentialsMatcher());
    }

    /**
     * 设置了
     * (1)认证缓存的名称
     * (2)CacheManager
     * (3)CredentialsMatcher
     *
     * @param cacheManager
     * @param credentialsMatcher
     */
    public AuthenticatingRealm(CacheManager cacheManager, CredentialsMatcher credentialsMatcher) {

        authenticationTokenClass = UsernamePasswordToken.class;

        //保留对Shiro 1.1和更早版本的向后兼容性。 默认设置为true可能会导致
        this.authenticationCachingEnabled = false;

        int instanceNumber = INSTANCE_COUNT.getAndIncrement();
        this.authenticationCacheName = getClass().getName() + DEFAULT_AUTHEN_CACHE_SUFFIX;

        if (instanceNumber > 0) {
            this.authenticationCacheName = this.authenticationCacheName + "." + instanceNumber;
        }

        if (cacheManager != null) {
            super.setCacheManager(cacheManager);
        }

        if (credentialsMatcher != null) {
            this.setCredentialsMatcher(credentialsMatcher);
        }

    }


    //------------------------------------构造函数  END-------------------------------------------

    //------------------------------------get And set  Start-------------------------------------------

    public Class<? extends AuthenticationToken> getAuthenticationTokenClass() {
        return authenticationTokenClass;
    }

    public void setAuthenticationTokenClass(Class<? extends AuthenticationToken> authenticationTokenClass) {
        this.authenticationTokenClass = authenticationTokenClass;
    }

    public CredentialsMatcher getCredentialsMatcher() {
        return credentialsMatcher;
    }

    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        this.credentialsMatcher = credentialsMatcher;
    }

    public String getAuthenticationCacheName() {
        return authenticationCacheName;
    }

    public void setAuthenticationCacheName(String authenticationCacheName) {
        this.authenticationCacheName = authenticationCacheName;
    }

    public boolean isAuthenticationCachingEnabled() {
        return authenticationCachingEnabled;
    }

    public void setAuthenticationCachingEnabled(boolean authenticationCachingEnabled) {
        this.authenticationCachingEnabled = authenticationCachingEnabled;
    }

    /**
     * Principal对应AuthenticationInfo
     *
     * @return
     */
    public Cache<Object, AuthenticationInfo> getAuthenticationCache() {
        return authenticationCache;
    }

    public void setAuthenticationCache(Cache<Object, AuthenticationInfo> authenticationCache) {
        this.authenticationCache = authenticationCache;
    }

    //------------------------------------get And set END----------------------------------------

    @Override
    public void setName(String name) {
        super.setName(name);
        if (this.authenticationCacheName != null && this.authenticationCacheName.startsWith(getClass().getName())) {
            //摆脱默认构造器的名称,重新定义名称,但是好像只能修改一次默认名称,因为第二个判断条件
            this.authenticationCacheName = name + DEFAULT_AUTHEN_CACHE_SUFFIX;
        }
    }

    /**
     * 核心:获取认证信息
     * (1)首先用token去缓存Cache中拿缓存的info
     * 前置:怎么确保Cache对象一定有?
     * 解决:在设置CacheManager最后一步触发 afterCacheManagerSet()方法
     * 看是否开启了认证缓存,如果开启,则创建CacheManager并懒加载Cache
     * (2)如果info为空,则提供抽象方法让子类去通过token去查询info
     * 前置:如何保证Cache被初始化。手法与上同
     * 后置:如果查到info,则尝试缓存token对应的info
     * (3) 用info与token进行对比
     * <p>
     * 其它几乎所有方法都为此方法服务
     *
     * @param token 认证签名
     * @return
     * @throws AuthenticationException
     */
    @Override
    public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //核心:让子类通过token获取到info
            info = doGetAuthenticationInfo(token);
            //如果token能够查到info,那么去对比一下,看看是否正确
            if (token != null && info != null) {
                LOGGER.debug("无缓存信息,尝试做token与info的缓存");
                //尝试缓存一下info
                cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            //说明有缓存
            LOGGER.debug("从缓存中获取Info信息:{}", info);
        }

        if (info != null) {
            LOGGER.debug("校验info与token");
            assertCredentialsMatch(token, info);
        } else {
            LOGGER.debug("通过token压根就没找到info");
        }
        //返回有info对象,说明已经校验通过
        return info;
    }


    @Override
    public void init() throws ShiroException {
        onInit();
    }

    @Override
    protected void doClearCache(PrincipalCollection principals) {
        clearCachedAuthenticationInfo(principals);
    }

    protected void clearCachedAuthenticationInfo(PrincipalCollection principalCollection) {
        if (!CollectionUtils.isEmpty(principalCollection)) {
            Cache<Object, AuthenticationInfo> cache = getAuthenticationCache();
            if (cache != null) {
                Object key = getAuthenticationCacheKey(principalCollection);
                cache.remove(key);
            }
        }
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        //我是你爹吗?
        //instance:我是你儿子吗?
        return token != null && this.authenticationTokenClass.isAssignableFrom(token.getClass());
    }

    /**
     * 核心:判断用户输入是否正确
     *
     * @param token 用户输入信息 提交的认证令牌
     * @param info  数据库存放数据
     *              与给定的对应的AuthenticationInfo
     * @throws AuthenticationException
     */
    protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
        CredentialsMatcher cm = getCredentialsMatcher();
        if (cm != null) {
            if (!cm.doCredentialMatch(token, info)) {
                //认证失败,抛出异常
                throw new IncorrectCredentialsException("提交的" + token.getPrincipal() + "帐号或密码错误");
            }
        } else {
            throw new AuthenticationException("Realm没有配置credentialsMatcher对象");
        }
    }

    /**
     * 重写的方法用protected比较清楚
     */
    protected void onInit() {
    }

    /**
     * 是否开启认证缓存
     */
    protected boolean isAuthenticationCachingEnabled(AuthenticationToken token, AuthenticationInfo info) {
        return isAuthenticationCachingEnabled();
    }

    /**
     * 缓存管理器设置后,获取可用的认证缓存
     * 此方法会根据是否开启缓存,创建一个叫做 this.authenticationCacheName 的 Cache
     */
    @Override
    protected void afterCacheManagerSet() {
        getAvailableAuthenticationCache();
    }

    /**
     * 说白了,获取token的username
     * token.getPrincipal()
     */
    protected Object getAuthenticationCacheKey(AuthenticationToken token) {
        if (token == null) {
            return null;
        }
        return token.getPrincipal();
    }

    protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
        return getAvailablePrincipal(principals);
    }


    /**
     * 核心:用户重写通过token获取到info
     *
     * @param token token
     * @return info
     */
    protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token);

    private void cacheAuthenticationInfoIfPossible(AuthenticationToken token, AuthenticationInfo info) {
        if (!isAuthenticationCachingEnabled()) {
            LOGGER.debug("未开启认证缓存");
            return;
        }
        //获取可用的缓存
        Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
        if (cache != null) {
            //如果缓存不为空,说明有CacheManager并创建了Cache,也把Cache放入了CacheManager中
            Object key = getAuthenticationCacheKey(token);
            //将token.getPrincipal()与info缓存
            cache.put(key, info);
        }
    }

    private Cache<Object, AuthenticationInfo> getAuthenticationCacheLazy() {

        if (this.authenticationCache == null) {
            LOGGER.debug("创建认证缓存");
            //拿到缓存声明周期管理对象,由CacheRealm提供
            CacheManager cacheManager = getCacheManager();
            if (cacheManager != null) {
                String cacheName = getAuthenticationCacheName();
                LOGGER.debug("缓存管理器 [{}] 创建.  创建认证缓存 '{}'", cacheManager, cacheName);
                //创建一个缓存,没有此名字的缓存则创建一个
                this.authenticationCache = cacheManager.getCache(cacheName);
            }
        }
        //返回此缓存对象,到底缓存什么,缓存Object(principal)对应的authenticationInfo
        return this.authenticationCache;
    }

    /**
     * 获取可用的认证缓存
     *
     * @return
     */
    private Cache<Object, AuthenticationInfo> getAvailableAuthenticationCache() {
        //获取内部缓存
        Cache<Object, AuthenticationInfo> cache = getAuthenticationCache();
        //判断缓存认证缓存是否开启
        boolean authcCachingEnabled = isAuthenticationCachingEnabled();
        if (cache == null && authcCachingEnabled) {
            //如果缓存是空,并且开启了缓存,使用懒加载方式加载一个缓存
            cache = getAuthenticationCacheLazy();
        }
        return cache;
    }

    /**
     * 根据token拿到缓存对象
     *
     * @param token 用户输入的token
     * @return 缓存对象
     */
    private AuthenticationInfo getCachedAuthenticationInfo(AuthenticationToken token) {
        AuthenticationInfo info = null;

        Cache<Object, AuthenticationInfo> cache = this.authenticationCache;
        if (cache != null && token != null) {
            LOGGER.debug("尝试获取AuthenticationInfo缓存");
            Object key = getAuthenticationCacheKey(token);
            info = cache.get(key);
            if (info == null) {
                LOGGER.info("暂无认证缓存");
            } else {
                LOGGER.info("为:{}找到AuthenticationInfo缓存", key);
            }
        }
        return info;
    }


}

发布了315 篇原创文章 · 获赞 243 · 访问量 26万+

猜你喜欢

转载自blog.csdn.net/yanluandai1985/article/details/103643613
今日推荐