• Spring-shiro源码陶冶-AuthorizingRealm用户认证以及授权


    阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用

    简单介绍

    Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能

    AuthorizingRealm的继承关系

    Realm->CachingRealm->AuthenticatingRealm->AuthorizingRealm
    本文只针对以上关系进行讲解,其余的实现类请自行查看源码

    Realm接口类

    Realm提供了安全的访问应用的相关实体类,比如用户、角色、权限,对其中的访问应用相应的认证或者授权操作。其提供的主要的方法为AuthenticationInfo#getAuthenticationInfo,涉及的内容是关于信息的认证,这主要由AuthencatingRealm类实现

    CachingRealm抽象类

    提供缓存功能,简单看下其下的变量以及主要方法。

    • 相关变量如下
    	//设置是否允许缓存,构造函数中默认为true
    	private boolean cachingEnabled;
    	//设置缓存管理器,需要另外引入
        private CacheManager cacheManager;
    
    • 主要方法
    1. CachingRealm#afterCacheManagerSet
      默认实现为空,供子类调用,在设置cacheManager变量调用此方法
    2. CachingRealm#getAvailablePrincipal
    //获取principal对象,一般都是子类在执行授权操作赋予的
    protected Object getAvailablePrincipal(PrincipalCollection principals) {
            Object primary = null;
            if (!CollectionUtils.isEmpty(principals)) {
                Collection thisPrincipals = principals.fromRealm(getName());
                if (!CollectionUtils.isEmpty(thisPrincipals)) {
                    primary = thisPrincipals.iterator().next();
                } else {
                    //no principals attributed to this particular realm.  Fall back to the 'master' primary:
                    primary = principals.getPrimaryPrincipal();
                }
            }
    
            return primary;
        }
    

    AuthenticatingRealm-验证Realm抽象类

    简单分析下其主要私有变量以及方法

    • 主要参数
    //与父类的cachingEnabled结合使用,在构造函数里其默认是false
    private boolean authenticationCachingEnabled;
    //用户凭证验证类,比如校验密码是否一致
    private CredentialsMatcher credentialsMatcher;
    //认证Token类,默认为UsernamePasswordToken类
    private Class<? extends AuthenticationToken> authenticationTokenClass;
    
    • 主要方法
    1. AuthenticatingRealm#setAuthenticationCachingEnabled-设置缓存是否许可
    //可见如果只设置authenticationCachingEnable,其为true,也会设置cachingEnable=true
    public void setAuthenticationCachingEnabled(boolean authenticationCachingEnabled) {
            this.authenticationCachingEnabled = authenticationCachingEnabled;
            if (authenticationCachingEnabled) {
                setCachingEnabled(true);
            }
        }
    
    1. AuthenticatingRealm#isAuthenticationCachingEnabled
    //可见是否应用缓存是根据authenticationCachingEnabled和cachingEnabled联合判断的
    public boolean isAuthenticationCachingEnabled() {
            return this.authenticationCachingEnabled && isCachingEnabled();
        }
    
    1. AuthenticatingRealm#getAuthenticationInfo
      认证接口实现方法,该方法的回调一般是通过subject.login(token)方法来实现的
    public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    		//获取缓存中的认证信息,其中也会涉及到调用isAuthenticationCachingEnabled
            AuthenticationInfo info = getCachedAuthenticationInfo(token);
            if (info == null) {
                //otherwise not cached, perform the lookup:
                //调用doGetAuthenticationInfo方法,此处为抽象类,供子类调用
                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;
        }
    
    1. AuthenticatingRealm#doGetAuthenticationInfo
      获取认证信息方法,抽象方法供子类实现
    protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException ;
    
    1. AuthenticatingRealm#assertCredentialsMatch
      对凭证信息的校验,涉及到加密方式
    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 {
    	        //可见credentialsMatcher属性必须要设定,否则会抛异常
                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.");
            }
        }
    

    AuthorizingRealm-授权抽象类

    其中的内容和其父类验证抽象类基本相似,这里就不赘述了,主要提下主要方法

    1. AuthorizingRealm#getAuthorizationInfo
    //授权
    protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
    
            if (principals == null) {
                return null;
            }
    
            AuthorizationInfo info = null;
    
            if (log.isTraceEnabled()) {
                log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
            }
    		//是否引用cache
            Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
            if (cache != null) {
                if (log.isTraceEnabled()) {
                    log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
                }
                Object key = getAuthorizationCacheKey(principals);
                info = cache.get(key);
                if (log.isTraceEnabled()) {
                    if (info == null) {
                        log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
                    } else {
                        log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
                    }
                }
            }
    
    
            if (info == null) {
                // Call template method if the info was not found in a cache
                //调用授权抽象方法,供子类实现
                info = doGetAuthorizationInfo(principals);
                // If the info is not null and the cache has been created, then cache the authorization info.
                if (info != null && cache != null) {
                    if (log.isTraceEnabled()) {
                        log.trace("Caching authorization info for principals: [" + principals + "].");
                    }
                    Object key = getAuthorizationCacheKey(principals);
                    cache.put(key, info);
                }
            }
    
            return info;
        }
    
    1. AuthorizingRealm#doGetAuthorizationInfo
      真实授权抽象方法,供子类调用
    protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
    

    示例

    下述示例结合了授权与验证,即继承AuthorizingRealm即可

    1. spring-shiro配置文件样例
    <!--缓存管理器-->
    <bean  id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean>
        <!-- 凭证匹配器 -->
        <bean id="credentialsMatcher" class="com.jing.test.cas.admin.core.shiro.RetryLimitHashedCredentialsMatcher">
            <constructor-arg ref="cacheManager"/>
            <property name="hashAlgorithmName" value="md5"/>
            <property name="hashIterations" value="2"/>
            <property name="storedCredentialsHexEncoded" value="true"/>
        </bean>
        
        <!--密码处理类-->
        <bean id="passwordService" class="com.jing.test.cas.admin.core.shiro.ShiroPasswordService"></bean>
        <!-- Realm实现 -->
        <bean id="shiroDBRealm" class="com.jing.test.cas.admin.core.shiro.ShiroDBRealm">
            <property name="credentialsMatcher" ref="credentialsMatcher"/>
            <property name="cachingEnabled" value="false"/>
            <property name="shiroPasswordService" ref="passwordService"/>
            <property name="shiroUserService" ref="shiroUserService"/>
        </bean>
    <!-- 安全管理器 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="shiroDBRealm"/>
            <property name="sessionManager" ref="sessionManager"/>
        </bean>
        <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
        <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
            <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
            <property name="arguments" ref="securityManager"/>
        </bean>
    <!-- Shiro的Web过滤器 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager"/>
            <property name="loginUrl" value="/login"/>
            <property name="successUrl" value="/index.html"/>
            <property name="unauthorizedUrl" value="/403.html"/>
           <property name="filters">
                <util:map>
                    <entry key="authc" value-ref="formAuthenticationFilter"/>
                    <entry key="perm" value-ref="permissionsAuthorizationFilter"/>
                    <entry key="captcha" value-ref="captchaValidateFilter"/>
                    <entry key="sysUser" value-ref="sysUserFilter"/>
                    <entry key="user" value-ref="userFilter"/>
                </util:map>
            </property>
            <property name="filterChainDefinitions">
            	<!-- 如果不应用acm cas方案则添加	/logout=logout -->
                <value>
                	/ossmanager/api/** = anon
                    /test/** = anon
                    /login = captcha,authc
                    /logout=logout
                    /index = anon
                    /jcaptcha.jpeg = anon
                    /403.html = anon
                    /login.html = anon
                    /favicon.ico = anon
                    /static/** = anon
                    /index.html=user,sysUser
                    /welcome.html=user,sysUser
                    /admin/user/modifyPwd.html=user,sysUser
                    /admin/user/updatePassword=user,sysUser
                    /admin/user/role/list=user,sysUser
                    /** = user,sysUser,perm
                </value>
            </property>
        </bean>
    
        <!-- Shiro生命周期处理器-->
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    
    1. Realm接口实现类
    package com.jing.test.cas.admin.core.shiro;
    
    import com.jing.test.cas.admin.core.exceptions.BizException;
    import org.apache.commons.lang3.ArrayUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.Collections;
    import java.util.Set;
    /**
     *Realm接口实现类
     */
    public class ShiroDBRealm  extends AuthorizingRealm {
        /**
         * 日志对象
         */
        private static final Logger logger = LoggerFactory.getLogger(ShiroDBRealm.class);
    
        /**
         * 账户禁用
         */
        private static final String USER_STATUS_FORBIDDEN = "1";
    
        /**
         * 权限相关用户服务接口
         */
        private IShiroUserService shiroUserService;
    
        /**
         * 密码服务类 加密作用
         */
        private ShiroPasswordService shiroPasswordService;
    
        /**
         * 授权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            // 因为非正常退出,即没有显式调用 SecurityUtils.getSubject().logout()
            // (可能是关闭浏览器,或超时),但此时缓存依旧存在(principals),所以会自己跑到授权方法里。
            if (!SecurityUtils.getSubject().isAuthenticated()) {
                doClearCache(principalCollection);
                SecurityUtils.getSubject().logout();
                return null;
            }
            ShiroUser shiroUser = (ShiroUser)principalCollection.getPrimaryPrincipal();
            String userName = shiroUser.getUserName();
            if(StringUtils.isNotBlank(userName)){
                SimpleAuthorizationInfo sazi = new SimpleAuthorizationInfo();
                try {
                    Set<String> roleIds= shiroUserService.getRoles(userName);
                    for (String roleId: roleIds){
                        shiroUser.setRoleId(roleId);
                    }
                    sazi.addRoles(roleIds);
                    sazi.addStringPermissions(shiroUserService.getPermissions(userName));
                    return sazi;
                } catch (Exception e) {
                    logger.error(e.getMessage(),e);
                }
            }
            return null;
        }
    
        /**
         * 认证
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
            ShiroUser shiroUser = shiroUserService.getShiroUser(token.getUsername());
            checkUserStatus(shiroUser);
            if(shiroUser != null){
                SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(shiroUser,shiroUser.getPassword(),ByteSource.Util.bytes(shiroUser.getUserName()+shiroPasswordService.getPublicSalt()),getName());
                return authenticationInfo;
            }
            return null;
        }
    
        /**
         * 检查用户状态
         * @param shiroUser
         */
        private void checkUserStatus(ShiroUser shiroUser) {
            if(StringUtils.equalsIgnoreCase(shiroUser.getUserStatus(),USER_STATUS_FORBIDDEN)){
                throw new ForbiddenException("用户已被禁用");
            }
        }
    
        /**
        * 初始化方法
         * 设定Password校验的Hash算法与迭代次数.
         **/
        public void initCredentialsMatcher() {
            HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(
                    shiroPasswordService.getHashAlgorithm());
            matcher.setHashIterations(shiroPasswordService.getHashInterations());
            setCredentialsMatcher(matcher);
        }
    
        public void setShiroUserService(IShiroUserService shiroUserService) {
            this.shiroUserService = shiroUserService;
        }
    
        public void setShiroPasswordService(ShiroPasswordService shiroPasswordService) {
            this.shiroPasswordService = shiroPasswordService;
        }
    }
    
    

    总结

    授权与验证的逻辑做了简单的了解,其中授权可通过subject.isPermited()等方法调用;验证则可通过subject.login()等方法来调用

  • 相关阅读:
    Django orm self 自关联表
    postgresql数据库导入导出
    celery在项目中的使用
    P3405 [USACO16DEC]Cities and States S 【map使用】
    P1030 求先序排列 【已知中序后序求先序】
    P1305 新二叉树 【寻找根节点进行先序遍历】
    P1229 遍历问题 【已知先序后序求中序种类】
    P1364 医院设置 【带权值的树的重心】
    P3884 [JLOI2009]二叉树问题 【离线tarjan或数的向上遍历】
    P1827 [USACO3.4]美国血统 American Heritage【树的遍历】
  • 原文地址:https://www.cnblogs.com/question-sky/p/6806419.html
Copyright © 2020-2023  润新知