zoukankan      html  css  js  c++  java
  • Shiro的几个关键类

    Shiro在于Spring集成中,需要配置SecurityManager,Realm,ShiroFilterFactoryBean这三个类。在Web环境中SecurityManager一般配置DefaultWebSecurityManager,如果需要扩展或者定制一些额外的功能,可以配置DefaultWebSecurityManager的继承类;Realm需要先继承AuthorizingRealm抽象类再配置,如果有多个Realm的话,还需要配置ModularRealmAuthenticator的继承实现类;ShiroFilterFactoryBean主要是提供ShiroFilter,可以配置一些资源的拦截。下面对一些核心类进行一下总结。

    SecurityManager

    该类继承了三个接口,还额外提供登录,退出和创建用户的功能。

     /**
      * 所有与安全有关的操作都会与SecurityManager交互
      * 扩展了authenticator、authorizer和sessionmanager接口
      */
    public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
    
        Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;
        
        void logout(Subject subject);
      
        Subject createSubject(SubjectContext context);
    }
    
    /**
     * 认证验证,登录校验
     *
     */
    public interface Authenticator {
    
        /**
         * AuthenticationToken 登录未验证的数据
         * AuthenticationInfo 身份验证/登录过程相关的帐户信息。
         *
         */
        public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
                throws AuthenticationException;
    }
    
    /**
     * 用户授权,权限校验
     *
     */
    public interface Authorizer {
    
        boolean[] isPermitted(PrincipalCollection subjectPrincipal, String... permissions);
    
        boolean[] isPermitted(PrincipalCollection subjectPrincipal, List<Permission> permissions);
    
        boolean isPermittedAll(PrincipalCollection subjectPrincipal, String... permissions);
    
        boolean isPermittedAll(PrincipalCollection subjectPrincipal, Collection<Permission> permissions);
    
        void checkPermissions(PrincipalCollection subjectPrincipal, String... permissions) throws AuthorizationException;
    
        void checkPermissions(PrincipalCollection subjectPrincipal, Collection<Permission> permissions) throws AuthorizationException;
    
        boolean hasRole(PrincipalCollection subjectPrincipal, String roleIdentifier);
    
        boolean[] hasRoles(PrincipalCollection subjectPrincipal, List<String> roleIdentifiers);
    
        boolean hasAllRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers);
    
        void checkRole(PrincipalCollection subjectPrincipal, String roleIdentifier) throws AuthorizationException;
    
        void checkRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers) throws AuthorizationException;
    
        void checkRoles(PrincipalCollection subjectPrincipal, String... roleIdentifiers) throws AuthorizationException;
        
    }
    
    /**
     * 会话管理
     */
    public interface SessionManager {
    
        /**
         * 基于指定的上下文初始化数据启动一个新Session,Session通常交由SessionFactory创建
         * 
         */
        Session start(SessionContext context);
    
        /**
         * 通过SessionKey查找Session
         *
         */
        Session getSession(SessionKey key) throws SessionException;
    }
    

    SecurityManager的Web部分源代码实现如下所示。从默认的构造器可以看到在创建SecurityManager的该实现时,会设置一系列默认的值,如ServletContainerSessionManager,CookieRememberMeManager等。而isHttpSessionMode方法判断是否是HttpSession,还是自己实现的Session。

    public class DefaultWebSecurityManager extends DefaultSecurityManager implements WebSecurityManager {
    
        public DefaultWebSecurityManager() {
            super();
            DefaultWebSessionStorageEvaluator webEvalutator = new DefaultWebSessionStorageEvaluator();  
            ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(webEvalutator);
            this.sessionMode = HTTP_SESSION_MODE;
            setSubjectFactory(new DefaultWebSubjectFactory());
            setRememberMeManager(new CookieRememberMeManager());
            setSessionManager(new ServletContainerSessionManager());
            webEvalutator.setSessionManager(getSessionManager());
        }
        
        public boolean isHttpSessionMode() {
            SessionManager sessionManager = getSessionManager();
            return sessionManager instanceof WebSessionManager && 					                        ((WebSessionManager)sessionManager).isServletContainerSessions();		
        }
        ...
    }
    

    以下是SecurityManager中实现SessionManager接口的实现类,从中可以看到SecurityManager并没有实际处理SessionManager接口的方法,而是采用组合模式,将实际的SessionManager作为SecurityManager的成员变量,实际处理还是交由sessionManager来处理。而且在SessionManager初始化完默认的DefaultSessionManager后(在新继承的DefaultWebSecurityManager的类中,为ServletContainerSessionManager)后,如果SessionManager实现CacheManagerAware接口,则会将CacheManager也一同设置到SessionManager中。

    public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
        private SessionManager sessionManager;
    
        public SessionsSecurityManager() {
            super();
            this.sessionManager = new DefaultSessionManager();
            applyCacheManagerToSessionManager();
        }
    
         protected void applyCacheManagerToSessionManager() {
            if (this.sessionManager instanceof CacheManagerAware) {
                ((CacheManagerAware) this.sessionManager).setCacheManager(getCacheManager());
            }
        }
    
        public void setSessionManager(SessionManager sessionManager) {
            this.sessionManager = sessionManager;
            afterSessionManagerSet();
        }
    
        protected void afterSessionManagerSet() {
            applyCacheManagerToSessionManager();
            applyEventBusToSessionManager();
        }
    
        public SessionManager getSessionManager() {
            return this.sessionManager;
        }
    
        public Session start(SessionContext context) throws AuthorizationException {
            return this.sessionManager.start(context);
        }
    
        public Session getSession(SessionKey key) throws SessionException {
            return this.sessionManager.getSession(key);
        }
    }
    

    从SecurityManager的继承体系来看,每次的继承都会添加一个成员变量,并且对外公开的方法也是由该成员来处理。所以现在来看,SecurityManager是通过继承体系和组合的模式,来充实它的实际功能,并且将Shiro的各个组件都联系到了一起。SecurityManager是线程安全且真个应用只需要一个即可,因此Shiro提供了SecurityUtils让我们绑定它为全局的,方便后续操作。

    Realm

    Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成 DataSource,即安全数据源。 通常由程序实现AuthorizingRealm类,如果有多个实现,还需要重写ModularRealmAuthenticator的doAuthenticate的方法,来指定Realm对应处理的AuthenticationToken。另外AuthorizingRealm提供设置缓存,加密和权限的相关功能。

    public interface Realm {
    
    	/**
    	 * 返回应用中Realm的唯一名字
    	 */
        String getName();
    
        /**
    	 * 多Realm中,该Realm是否匹配AuthenticationToken
    	 */
        boolean supports(AuthenticationToken token);
    
        /**
    	 * 依据未认证的AuthenticationToken,返回认证后的AuthenticationInfo
    	 */
        AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
    
    }
    
    

    ModularRealmAuthenticator

    Authenticator的功能是验证用户帐号,是Shiro API中身份认证核心的入口点。如果验证成功,将返回 AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException实现异常。它的默认实现类是ModularRealmAuthenticator,可以从这个类中看到校验的整个流程,而且还提供了AuthenticationListener来监听认证的过程(主要有登录成功事件,登录失败事件和退出事件)。

    public class ModularRealmAuthenticator extends AbstractAuthenticator {
    
        private static final Logger log = LoggerFactory.getLogger(ModularRealmAuthenticator.class);
    
        private Collection<Realm> realms;
    
        private AuthenticationStrategy authenticationStrategy;
    
        public ModularRealmAuthenticator() {
            this.authenticationStrategy = new AtLeastOneSuccessfulStrategy();
        }
    
        public void setRealms(Collection<Realm> realms) {
            this.realms = realms;
        }
    
        protected Collection<Realm> getRealms() {
            return this.realms;
        }
    
        public AuthenticationStrategy getAuthenticationStrategy() {
            return authenticationStrategy;
        }
    
        public void setAuthenticationStrategy(AuthenticationStrategy authenticationStrategy) {
            this.authenticationStrategy = authenticationStrategy;
        }
    
        protected void assertRealmsConfigured() throws IllegalStateException {
            Collection<Realm> realms = getRealms();
            if (CollectionUtils.isEmpty(realms)) {
                String msg = "Configuration error:  No realms have been configured!  One or more realms must be " +
                        "present to execute an authentication attempt.";
                throw new IllegalStateException(msg);
            }
        }
    
        /**
         * 单Realm的校验,最后调用realm.getAuthenticationInfo方法来通过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;
        }
    
    	/**
         * 多Realm的校验,还需要考虑认证的策略(全部成功,至少一个成功)
         */
        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);
            }
        }
    
        public void onLogout(PrincipalCollection principals) {
            super.onLogout(principals);
            Collection<Realm> realms = getRealms();
            if (!CollectionUtils.isEmpty(realms)) {
                for (Realm realm : realms) {
                    if (realm instanceof LogoutAware) {
                        ((LogoutAware) realm).onLogout(principals);
                    }
                }
            }
        }
    }
    
    

    SessionManager

    SecurityManager提供了如下接口,另外用于 Web 环境的 WebSessionManager又提供了如下接口,判断是否是Servlet容器的Session,还是自己维护Session。SecurityManager管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。

    public interface SessionManager {
    	/**
    	 * 启动会话
    	 */
        Session start(SessionContext context);
      
        Session getSession(SessionKey key) throws SessionException;
    }
    
    public interface WebSecurityManager extends SecurityManager {
    
        boolean isHttpSessionMode();
    }
    
    

    在web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro 提供了会话验证调度器SessionValidationScheduler来做这件事情。SecurityManager的实现类中一般都实现了该接口。

    Shiro提供了SessionManager的三个默认实现:

    • DefaultSessionManager:DefaultSecurityManager 使用的默认实现,用于JavaSE环境

    • ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于 Web环境,其直接使用Servlet容器的会话;

    • DefaultWebSessionManager :用于Web环境的实现,可以代替ServletContainerSessionManager,自己维护着会话,直接废弃了 Servlet 容器的会话管理。

    Session

    Session是用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。如访问一些网站时登录成功后,网站可以记住用户,且在退出之前都可以识别当前用户是谁。Shiro的会话支持不仅可以在普通的JavaSE应用中使用,也可以在JavaEE应用中使用,如web应用。且使用方式是一致的 。

    public interface Session {
    
        Serializable getId();
    
        Date getStartTimestamp();
    
        Date getLastAccessTime();
    
        long getTimeout() throws InvalidSessionException;
    
        void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;
    
        String getHost();
    
        /**
         * 如果是 JavaSE 应用需要自己定期调用 session.touch()去更新最后访问时间;
         * 如果是 Web 应用,每次进入 ShiroFilter 都会自动调用 session.touch()来更新最后访问时间
         */
        void touch() throws InvalidSessionException;
    
        /**
    	 * 当Subject.logout()时会自动调用 stop 方法来销毁会话
         */
        void stop() throws InvalidSessionException;
    
        Collection<Object> getAttributeKeys() throws InvalidSessionException;
    
        Object getAttribute(Object key) throws InvalidSessionException;
     
        void setAttribute(Object key, Object value) throws InvalidSessionException;
    
        Object removeAttribute(Object key) throws InvalidSessionException;
    }
    

    Session提供了监听器SessionListener,用于监听会话创建、过期及停止事件,如果只想监听某一个事件,可以继承SessionListenerAdapter实现。

    Shiro提 SessionDAO用于会话的CRUD操作,AbstractSessionDAO提供了 SessionDAO的基础实现,如生成会话 ID等;CachingSessionDAO提供了对开发者透明的会话缓存的功能,只需要设置相应的 CacheManager 即可;MemorySessionDAO直接在内存中进行会话维护;而EnterpriseCacheSessionDAO提供了缓存功能的会话维护,但是都是空方法,需要继承实现这些方法。

    总结

    Shiro与Spring的整合中,很多对Shiro的功能扩展,都需要继承原来的类,再修改为默认的实现。比如在Web环境中可以自己实现Session管理,就需要在SecurityManager中调用setSessionManager()方法,修改默认的SessionManager。

    还有在Shiro和Spring整合中碰到了一个问题UserRealm中注入IUserService,导致IUserService的AOP失效(会导致事务失效等),只是查明了原有,解决办法可以不用注入,改为SpringContextHolder.getBean(IUserService.class)的方式。

  • 相关阅读:
    网络密钥交换协议——Diffie-Hellman
    【剑指Offer】俯视50题之1-10题
    分治
    《Java程序猿面试笔试宝典》之Java程序初始化的顺序是如何的
    China Final J
    MVC入门
    1,单例模式
    运行耗时统计
    普通方法调用,Invoke,begininvoke三者的区别总结及异步与同步的区别总结
    几种查询方法(lambda Linq Enumerable静态类方式)
  • 原文地址:https://www.cnblogs.com/fzsyw/p/11443740.html
Copyright © 2011-2022 走看看