zoukankan      html  css  js  c++  java
  • Java-Shiro(四):Shiro Realm讲解(一)Realm介绍

    Realm简介

    Realm:英文意思‘域’,在Shiro框架中,简单理解它存储(读取配置、读取数据库、读取内存中)用户信息,并且它充当了Shiro与应用安全数据间的“桥梁”(“连接器”),当对某个用户执行认证(登录)和授权(用户用有的角色、资源,或者说访问控制)验证时,Shiro会从应用配置的Realm中查找用户以及权限信息。

    从它的在Shiro矿建中所起的作用来讲,Realm相当于是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须指定一个Realm(当然允许配置多个,指定多Realm验证策略,多个Realm来工作),用于认证、授权。

    按照Realm中用户和权限信息存储方式不同,在Shiro框架内部定义了以下Realm:

    1)IniRealm:将用户、角色信息配置到*.ini文件中,使用IniRealm加载数据信息,提供认证、授权功能;

    2)PropertiesRealm:将用户、角色信息配置到*.properties文件中,使用PropertiesRealm加载数据信息,提供认证、授权功能;

    3)jdbcRealm:将用户、角色信息配置到关系型数据库总,外部可以指定DataSource信息,默认内部包含了认证、授权SQL,默认数据库表:users、user_roles、roles_permissions;

    4)*Ldap*/ActiveDirectory*:LDAP目录服务是由目录数据库和一套访问协议组成的系统,其实就是把用户、角色信息存储到这样的一个Ad系统,通过Ldap/Jndi/等方式连接读取数据,至于shiro功能与上边3种一致。

    5)自定义Realm:上图中MyRealm就是我自定义的一个Realm,如果缺省的Realm不能满足需求时,我们还可以自定义数据源自己的Realm实现。

    Realm功能

    Realm主要负责的功能包含以下:

    通过上边Realm类结构图,可以看出IniRealm、PropertiesRealm、JdbcRealm等的最上层父类是 AuthorizingRealm,而它又继承了AuthenticatingRealm,我们查看类中定义:

    AuthenticatingRealm.java中唯一抽象方法:(该方法提供身份认证使用,具体可以参考缺省Realm的实现)

    protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

    AuthorizingRealm.java中唯一抽象方法:(该方法给认证通过的用户赋值权限、资源使用,具体可以参考缺省Realm的实现)

    protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);

    1)身份认证:调用Realm#getAuthenticationinfo()方法,验证用户输入的账户和密码(内部调用Realm#doGetAuthenticationInfo()方法进行用户认证),并返回与Realm数据对比验证结果相关信息;查看AuthenticationRealm#getAuthenticationInfo()源码:

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

    2)权限、资源获取:调用Realm#getAuthorizationinfo()方法,获取指定身份的权限(内部调用Realm#doGetAuthorizationInfo()方法进行制定身份的权限),并返回相关信息;查看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<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;
        }

    3)判定Token是否支持:调用Realm#supports()方法,验证是否支持令牌(Token)。

    备注:Token在Shiro中包含几种类型:HostAuthenticationToken(主机验证令牌),UsernamePasswordToken(账户密码验证令牌),RememberMeAuthenticationToken(记录上次登录用户Token)。

    什么时候调用Realm#getAuthenticationinfo()、什么时候调用 Realm#getAuthorizationinfo()?

            Realm iniRealm = new IniRealm("classpath:shiroAuthorizer.ini");
            DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(iniRealm);
    
            SecurityUtils.setSecurityManager(defaultSecurityManager);
            Subject subject = SecurityUtils.getSubject();
    
            UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
            // 调用 Realm#getAuthenticationinfo()
            subject.login(token);
    
            System.out.println("是否认证通过:" + subject.isAuthenticated());
    
            // 调用 Realm#getAuthorizationinfo()
            System.out.println("是否授权admin角色:" + subject.hasRole("admin"));
            System.out.println("是否拥有user:update资源:" + subject.isPermitted("user:update"));
            System.out.println("是否拥有user:delete资源:" + subject.isPermitted("user:delete"));
            System.out.println("是否拥有user:create资源:" + subject.isPermitted("user:create"));
    
            subject.logout();
    
            System.out.println("是否认证通过:" + subject.isAuthenticated());
    
            System.out.println("是否授权admin角色:" + subject.hasRole("admin"));
            System.out.println("是否拥有user:update资源:" + subject.isPermitted("user:update"));
            System.out.println("是否拥有user:delete资源:" + subject.isPermitted("user:delete"));
            System.out.println("是否拥有user:create资源:" + subject.isPermitted("user:create"));

    1)什么时候调用身份认证(Realm#getAuthenticationinfo())

    从Subject#login()代码进行跟踪,寻找Subject#login()底层调用的代码如下:

    DelegatingSubject#login()方法调用-》DefaultSecurityManager#login(),内部调用-》AbstractAuthenticator#authenticate()方法,内部调用-》ModularRealmAuthenticator#doAuthenticate()方法,该方法源码如下:

        /**
         * Subject#login最终底层调用的方法就是该方法
         */
        protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
            assertRealmsConfigured();
            Collection<Realm> realms = getRealms();
            if (realms.size() == 1) {
                return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
            } else {
                return doMultiRealmAuthentication(realms, authenticationToken);
            }
        }
    
        /**
         * 单个Realm配置时,调用该法
         */
        protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
            if (!realm.supports(token)) {
                String msg = "Realm [" + realm + "] does not support authentication token [" +
                        token + "].  Please ensure that the appropriate Realm implementation is " +
                        "configured correctly or that the realm accepts AuthenticationTokens of this type.";
                throw new UnsupportedTokenException(msg);
            }
            AuthenticationInfo info = realm.getAuthenticationInfo(token);
            if (info == null) {
                String msg = "Realm [" + realm + "] was unable to find account data for the " +
                        "submitted AuthenticationToken [" + token + "].";
                throw new UnknownAccountException(msg);
            }
            return info;
        }
    
        /**
         * 多个Realm配置时,调用该方法
         */
        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;
        }

    备注:

    1)由于Subject是一个接口类型,因此上边代码找的是 DelegatingSubject.java类中的代码;

    2)Subject的类结构图:

    2)什么时候调用授权( Realm#getAuthorizationinfo())

    从调用Subject#isPermitted()源码可以看出内部调用了Subject#getAuthorizationInfo()方法:

        public boolean isPermitted(PrincipalCollection principals, Permission permission) {
            AuthorizationInfo info = getAuthorizationInfo(principals);
            return isPermitted(permission, info);
        }

    从调用Subject#hasRole()源码可以看出内部调用了Subject#getAuthorizationInfo()方法:

        public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
            AuthorizationInfo info = getAuthorizationInfo(principal);
            return hasRole(roleIdentifier, info);
        }

    不过,getAuthorizationInfo 的执行调用方式包括上面的总共有三个:

    1)subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去调用这个是否有什么角色或者是否有什么权限的时候;
    2)@RequiresRoles(“admin”) :在Controller方法上加注解的时候;
    3)<@shiro.hasPermission name = “admin”>html code</@shiro.hasPermission>:在页面上加shiro标签的时候,即进这个页面的时候扫描到有这个标签的时候。
    明。

    参考资料:

    Shiro 中的 Realm

    SSM整合shiro实现多用户表多Realm统一登录认证(大章附代码)

    不错的视屏教程,很实用:

    https://www.bilibili.com/video/av22573274/?p=13

    https://www.bilibili.com/video/av74805893?p=18 (针对java proj/springmvc整合/分布式用法都有系列讲解,值得推荐)

    文章实战:https://blog.csdn.net/bieleyang/article/category/7140250

  • 相关阅读:
    php 小试 mysql-zmq-plugin 和 pthreads
    svn:previous operation has not finished
    Http Header里的Content-Type
    sublime text使用及常见问题
    Less:优雅的写CSS代码
    gulp:更简单的自动化构建工具
    js实现『加载更多』功能实例
    JSONP浅析
    使用JSSDK集成微信分享遇到的一些坑
    JavaScript模板引擎实例应用
  • 原文地址:https://www.cnblogs.com/yy3b2007com/p/9212031.html
Copyright © 2011-2022 走看看