• 欢迎浏览“String me = Creater\忠实的资深Linux玩家;”,请文明浏览,理性发言,有侵犯你的权益请邮件我(creater@vip.qq.com).
  • 把任何的失败都当作一次尝试,不要自卑;把所有的成功都想成是一种幸运,不要自傲。
  •    3年前 (2015-05-24)  JAVA WEB shiro |   抢沙发  19 
    文章评分 0 次,平均分 0.0

    1. Session Management
    Shiro提供了安全框架所独有的特性:企业级session的解决方案。

    1.1 Using Sessions
    使用Subject获取Session:

    Subject currentUser = SecurityUtils.getSubject();
    Session session = currentUser.getSession();
    session.setAttribute( "someKey", someValue);
    

    和HttpServletRequest的API相似,Suject.getSession(boolean create)方法和HttpServletRequest.getSession(boolean create)有相同的逻辑:
    a. 如果当前的Subject已经有Session,返回当前Session,参数boolean被忽略。
    b. 如果当前的Subject没有Session,且参数boolean是true,创建新Session,并返回。
    c. 如果当前的Subject没有Session,且参数boolean是false,不创建新Session,直接返回null。
    有了Session,就可以进行任何操作,比如设置超时时间等。需要注意的是,此Session非HttpSession,也就是说,getSession 可以在非web环境下调用。

    2. The SessionManager
    SessionManager,是SecurityManager维护的核心组件,管理应用中所有的Session。默认的SecurityManager实现为DefaultSessionManager,它提供了企业级session管理的所有特性,像session验证,清理无效session等。可以通过securityManager的getSessionManager/setSessionManager方法获取和设置sessionManager,也可以使用INI文件配置:

    [main]
    ...
    sessionManager = com.foo.my.SessionManagerImplementation
    securityManager.sessionManager = $sessionManager
    

    需要注意的是,创建SessionManager相当复杂,Shiro默认提供的SessionManager可以满足大部分应用的需求。如果在web应用中,需要使用其他SessionManager实现。

    2.1 Session Timeout
    默认session的超时时间为30分钟,可以使用globalSessionTimeout设置一个全局的session超时时间。比如设置session超时时间为1小时:

    main]
    ...
    # 3,600,000 milliseconds = 1 hour
    securityManager.sessionManager.globalSessionTimeout = 3600000
    

    2.2 Per-Session Timeout
    globalSessionTimeout的设置是全局的,即对所有的session有效。也可以对单独session设置超时时间,使用session的timeout属性即可,单位也是毫秒。

    2.3 Session Listeners
    Shiro支持SessionListener的概念以允许你监听Session事件。可以通过实现SessionListener接口或继承SessionListenerAdapter类,然后设置SessionManager的sessionListeners属性即可,可以配置一或多个监听器:

    [main]
    ...
    aSessionListener = com.foo.my.SessionListener
    anotherSessionListener = com.foo.my.OtherSessionListener
    
    securityManager.sessionManager.sessionListeners = $aSessionListener, $anotherSessionListener, etc.
    

    需要注意的是,任何一个session事件的发生都会触发SessionListeners。

    2.4 Session Storage
    Session的创建和更新,需要将其内部的数据保存到"仓库里",session失效,也需要从"仓库里"删除其内部数据。SessionManager的实现中,会委托SessionDAO组件进行"仓库"的CRUD操作。
    可以自定义SessionDAO实现,将session数据存储在内存,文件系统,关系型数据库,NoSQL数据库,或任何其他地方(上面所说仓库,即指存储的位置),然后配置到SessionManager的属性里即可:

    [main]
    ...
    sessionDAO = com.foo.my.SessionDAO
    securityManager.sessionManager.sessionDAO = $sessionDAO
    

    以下是需要注意的地方:
    配置的securityManager.sessionManager.sessionDAO = $sessionDAO,只有在使用Shiro自带的session管理器时才起作用。在web环境下,应用会使用Servlet容器的session管理器,而导致配置的SessionDAO无效。如果想在web应用中使用SessionDAO,先配置Shiro自带的session管理器:

    [main]
    ...
    sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
    securityManager.sessionManager = $sessionManager
    
    # Configure a SessionDAO and then set it:
    securityManager.sessionManager.sessionDAO = $sessionDAO
    

    Shiro自带的session管理器将session存储在内存里。这不适用于生产环境。

    2.5 EHCache SessionDAO
    如果不想实现自己的SessionDAO,推荐使用EHCache。EHCache将session信息存储在内存里,当内存达到极限时,会转存到磁盘。这样保证了在生产环境下,不会出现session丢失的现象。EHCache还可以缓存认证和授权的信息。如果想不依赖容器做session的集群,EHCache也是一个很好的选择。在EHCache基础上加入Terracotta,即可实现与容器无关的session集群缓存。使用EHCache很简单,首先需要保证classpath里有shiro-ehcache-.jar,在INI中配置:

    [main]
    
    sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
    securityManager.sessionManager.sessionDAO = $sessionDAO
    
    cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
    securityManager.cacheManager = $cacheManager
    

    最后一行,securityManager.cacheManager = $cacheManager,配置了Shiro需要的CacheManager。由于EnterpriseCacheSessionDAO实现了CacheManagerAware接口,CacheManager会自动传入SessionDAO里。当SessionManager让EnterpriseCacheSessionDAO存储一个Session时,它将使用EHCache去存储session信息。

    2.6 EHCache Session Cache Configuration
    默认情况下,Shiro使用指定的ehcache.xml进行session缓存的配置。查看默认的ehcache.xml文件:

    <cache name="shiro-activeSessionCache"
           maxElementsInMemory="10000"
           overflowToDisk="true"
           eternal="true"
           timeToLiveSeconds="0"
           timeToIdleSeconds="0"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="600"/>
    

    如果想改变缓存的配置,需要定义相同的缓存键(cache entry)以满足Shiro的需求。也可以根据需求改变maxElementsInMemory的值。需要注意的是,至少要保证以下两个属性存在且不能更改:
    a. overflowToDisk="true" 这个属性保证了,当程序吃光内存时,sessions被保存到磁盘。
    b. eternal="true" 这个属性保证了缓存键(cache entry,即Session实例)永不过期,且不能被缓存自动删除。这个属性非常重要,因为Shiro的内部验证是基于一个定时程序,如果关闭这个属性,缓存可能会在Shiro不知的情形下清除无用的session,导致验证出现问题。

    2.7 EHCache Session Cache Name
    默认情况下,EnterpriseCacheSessionDAO会使用配置在ehcache.xml中缓存名为shiro-activeSessionCache的CacheManager。如果想改为其他键名,只需要保证ehcache.xml中的缓存名与INI文件中配置的相同即可:

    [main]
    ...
    sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
    sessionDAO.activeSessionsCacheName = myname
    ...
    

    2.8 Custom Session IDs
    默认的SessionDAO实现使用外部SessionIdGenerator组件产生Session ID。JavaUuidSessionIdGenerator为默认SessionIdGenerator的实现,使用UUIDs产生一个字符串格式的ID,它适用于生产环境。也可以通过实现SessionIdGenerator接口自定义session id的生成策略,然后配置到SessionDAO里即可:

    ...
    sessionIdGenerator = com.my.session.SessionIdGenerator
    securityManager.sessionManager.sessionDAO.sessionIdGenerator = $sessionIdGenerator
    

    2.9 Session Validation & Scheduling
    Session必须有效,无效的session会被删除。这保证了,随着时间的持久,存储空间不会被占满,且session不会被重用。由于性能的原因,只有在判断session是否过期或失效时才会被验证,这意味着,如果没有周期性的验证,session orphans(指僵尸session,下面会解释)将占满存储空间。一个普通的情形可以描述僵尸session:比如,一个用户登录一个系统,session被创建。如果用户没有注销而直接关闭浏览器,session还保存在服务器的存储空间里。SessionManager无法知道这个用户已经不在使用浏览器,那么这个session就无法被再次使用了,就变为僵尸session了。
    如果不定期的清理僵尸session,它将占满存储空间。为了防止僵尸session占满空间,SessionManager的实现提供了SessionValidationScheduler的支持。SessionValidationScheduler负责周期性的验证session以达到清除僵尸session目的。

    2.10 Default SessionValidationScheduler
    SessionValidationScheduler的默认实现ExecutorServiceSessionValidationScheduler支持所有的环境,它使用JDK的ScheduledExecutorService去控制验证的频率。默认情况下,一小时验证一次。可以通过配置一个新的ExecutorServiceSessionValidationScheduler来改变验证的间隔,单位是毫秒:

    [main]
    ...
    sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler
    # Default is 3,600,000 millis = 1 hour:
    sessionValidationScheduler.interval = 3600000
    
    securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler
    
    

    2.11 Custom SessionValidationScheduler
    也可以自定义SessionValidationScheduler实现,然后配置到SessionManager里即可:

    [main]
    ...
    sessionValidationScheduler = com.foo.my.SessionValidationScheduler
    securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler
    

    2.12 Disabling Session Validation
    某些情况下,想关闭周期性验证session的功能,因为已在程序外控制了session的验证(比如通过企业级缓存的实时功能去清理过期的session,或使用自定义任务去验证),这种情况下,就需要关闭它:

    [main]
    ...
    securityManager.sessionManager.sessionValidationSchedulerEnabled = false
    

    这只是关闭了Shiro周期性对session验证的功能,当session信息从存储空间取出时,依旧先执行验证操作。

    2.13 Invalid Session Deletion
    正如上面我们所说,周期性验证session的目的就是删除那些无效的session。默认情况下,只要Shiro检测到有无效的session,它就调用SessionDAO.delete(session)方法试图从存储空间删除这个session。这是保证应用的存储空间不被耗尽的最佳实践。然而,有些应用不想Shiro自动删除session。比如,应用程序提供了SessionDAO用于查询存储空间,需要查询那些过期和失效的session(像在过去的一周,一个用户创建了多少sessions,或者用户平均创建session的数量以及类似报告性查询),这种情况下,你需要完全的关闭session的删除功能:

    [main]
    ...
    securityManager.sessionManager.deleteInvalidSessions = false
    

    需要注意的是,如果你配置Shiro让其不删除失效的sessions,为了保证存储空间不被耗尽,你需要自己删除那些失效的session。还有一点值得注意,关闭session删除和关闭session周期性验证是不一样的。

    3. Session Clustering
    让人振奋人心的是,Shiro自带的session支持session集群,且不依赖容器环境。也就是说,如果你使用Shiro自带的session并配置了session集群,可以在Jetty,Tomcat,Jboss,Geronimo或任何其他环境下发布。Shiro是如何做到的?由于Shiro的N层POJO结构,Session集群就像Session持久化那样简单。如果配置了带有集群功能的SessionDAO,SessionDAO就负责和集群应用进行交互。

    3.1 Distributed Caches
    分布式缓存,例如: Ehcache+Terracotta, GigaSpaces Oracle Coherence,和 Memcached以及大多数的缓存,已经解决了分布式数据的持久化问题。因此,在Shiro中配置session集群,就是简单的配置一下分布式缓存。需要注意的是,当使用分布式缓存进行session存储,需要保证以下两点中必须有一项为真:
    a. 分布式缓存有集群级的内存供所有活跃的session。
    b. 如果分布式缓存没有集群级的内存来保存所有活跃的session,必须支持磁盘溢出,以防止session丢失。
    不能保证其中的一点,就会出现session丢失问题。

    3.2 EnterpriseCacheSessionDAO
    Shiro提供了可以将数据持久化到分布式缓存的SessionDAO实现-EnterpriseCacheSessionDAO,只需要把CacheManager或Cache配置到EnterpriseCacheSessionDAO里即可:

    #This implementation would use your preferred distributed caching product's APIs:
    activeSessionCache = my.org.apache.shiro.cache.CacheImplementation
    
    sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
    sessionDAO.activeSessionCache = $activeSessionCache
    
    securityManager.sessionManager.sessionDAO = $sessionDAO
    

    尽管可以向SessionDAO里注入Cache实例,但通常情况下,是配置CacheManager,因为CacheManager可以满足所有缓存的需求,只需将CacheManager中使用的缓存名字配置到EnterpriseCacheSessionDAO里即可:

    # This implementation would use your caching product's APIs:
    cacheManager = my.org.apache.shiro.cache.CacheManagerImplementation
    
    # Now configure the EnterpriseCacheSessionDAO and tell it what
    # cache in the CacheManager should be used to store active sessions:
    sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
    # This is the default value.  Change it if your CacheManager configured a different name:
    sessionDAO.activeSessionsCacheName = shiro-activeSessionsCache
    # Now have the native SessionManager use that DAO:
    securityManager.sessionManager.sessionDAO = $sessionDAO
    
    # Configure the above CacheManager on Shiro's SecurityManager
    # to use it for all of Shiro's caching needs:
    securityManager.cacheManager = $cacheManager
    

    注意到上面的配置有点奇怪么?我们没有配置sessionDAO使用哪个Cache或CacheManager,它是怎么发现缓存并使用它们的呢?Shiro在初始化SecurityManager时,它会检查SessionDAO是否实现了CacheManagerAware接口。如果实现,它就会自动使用配置的全局CacheManager。而EnterpriseCacheSessionDAO实现了CacheManagerAware接口,SecurityManager会把配置的cacheManager(securityManager.cacheManager = $cacheManager)通过参数的形式传给EnterpriseCacheSessionDAO的setCacheManager方法。运行时,如果EnterpriseCacheSessionDAO需要activeSessionsCache,它会在CacheManager中通过键-activeSessionsCache进行查找,得到Cache实例,并使用缓存。

    3.3 Ehcache + Terracotta
    一个众所周知的分布式缓存方案就是Ehcache + Terracotta。如果你对Terracotta + Ehcache的分布式很了解的话,Shiro这部分的配置就很简单。由于添加了Terracotta,需要在ehcache.xml文件中加入Terracotta的配置。以下这个配置经过测试,可以正确运行:

    <ehcache>
        <terracottaConfig url="localhost:9510"/>
        <diskStore path="java.io.tmpdir/shiro-ehcache"/>
        <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120">
            <terracotta/>
       </defaultCache>
       <cache name="shiro-activeSessionCache"
           maxElementsInMemory="10000"
           eternal="true"
           timeToLiveSeconds="0"
           timeToIdleSeconds="0"
           diskPersistent="false"
           overflowToDisk="false"
           diskExpiryThreadIntervalSeconds="600">
           <terracotta/>
       </cache>
       <!-- Add more cache entries as desired, for example,
            Realm authc/authz caching: -->
    </ehcache>
    

    可以通过改变将引用指向你的Terracotta服务器。还有一点需要主要,和之前的配置不一样,必须将diskPersistent和overflowToDisk属性设为false,因为true在集群环境下不支持。
    将这部分内容保存到ehcache.xml后,放在classpath路径下,然后打开Terracotta+Ehcache配置:

    sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
    # This name matches a cache name in ehcache.xml:
    sessionDAO.activeSessionsCacheName = shiro-activeSessionsCache
    securityManager.sessionManager.sessionDAO = $sessionDAO
    
    # Configure The EhCacheManager:
    cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
    cacheManager.cacheManagerConfigFile = classpath:ehcache.xml
    
    # Configure the above CacheManager on Shiro's SecurityManager
    # to use it for all of Shiro's caching needs:
    securityManager.cacheManager = $cacheManager
    

    需要记住,顺序很重要。通过把cacheManager配置在securityManager里,保证了所有之前配置的CacheManagerAware组件能够传入CacheManager。

    4. Sessions and Subject State
    a. Stateful Applications (Sessions allowed)
    默认情况下,SecurityManager使用Session存储Subject的标识信息和认证状态,这有以下几种好处:
    1. 所有的应用程序对服务的请求,调用都和session ID关联,这对于Shiro将用户和请求关联十分重要。比如,通过调用Subject.Builder获取其关联的Subject:

    Serializable sessionId = //get from the inbound request or remote method invocation payload
    Subject requestSubject = new Subject.Builder().sessionId(sessionId).buildSubject();
    

    这对于web应用和开发远程调用框架或消息服务框架非常有用。

    2. 任何在初始化请求"RememberMe"得到的标识都可以在第一次访问时保存到session里。这保证了Subject的remembered标识可以通过请求被保存而无需每次都反序列化或解码。比如,在web应用中,如果标识已存在session里,就无需每次请求时读取加密过的RememberMe cookie。这是一个不错的性能增强。

    b. Stateless Applications (Sessionless)
    对于大部分的应用来说,默认策略都可以满足,除了应用中有无状态的需求。好多无状态的架构不允许在请求时持久化状态。那么Subject状态就无法通过请求保存,这要求应用程序必须在其他地方保证Subject的状态。这种场景一般发生在,对每次请求、调用都需要进行验证时,比如很多无状态的应用通过强制进行HTTP的验证,让浏览器代替用户进行验证。

    4.1 Disabling Subject State Session Storage
    从Shiro的1.2及其以后版本,如果想关闭Subject的持久化,可以这样做:

    [main]
    ...
    securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false
    ...
    

    这将使Shiro无法从每次的请求/调用中使用session存储subject的状态。这意味着,你每次请求都需要认证,Shiro才能知道当前的Subject是谁。

    4.2 A Hybrid Approach
    上面的配置:securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false关闭了Shiro使用Session作为所有Subject存储的策略。假设存在某种情景,一些Subject的信息需要使用Session存储,而其他Subject不需要,怎么办呢?:
    a. 也许和人相关的Subject需要使用Session存储。
    b. 也许和人无关的Subject不需要使用Session存储。
    c. 也许所有和特殊类型或者对访问系统指定的位置使用session存储,其他的不需要。
    如果你需要混合的使用session存储策略,,可以实现SessionStorageEvaluator接口。

    4.3 SessionStorageEvaluator
    如果你想准确的控制哪些Subject需要他们的状态持久化到Session,可以实现SessionStorageEvaluator接口,这个接口只有一个方法:

    public interface SessionStorageEvaluator {
    
        public boolean isSessionStorageEnabled(Subject subject);
    
    }
    

    4.4 Subject Inspection
    当实现了SessionStorageEvaluator接口的isSessionStorageEnabled(subject)方法,可以对subject进行任何的操作,不过和应用环境相关的Subject才是最有用的。比如,在web应用中,如果数据必须基于ServletRequest存储,可以获取request或response,转换Subject实例(以为运行时Subject实例就为WebSubject实例),就会获取HttpServletRequest:

    public boolean isSessionStorageEnabled(Subject subject) {
            boolean enabled = false;
            if (WebUtils.isWeb(Subject)) {
                HttpServletRequest request = WebUtils.getHttpRequest(subject);
                //set 'enabled' based on the current request.
            } else {
                //not a web request - maybe a RMI or daemon invocation?
                //set 'enabled' another way...
            }
    
            return enabled;
        }
    

    4.5 Configuration
    实现了SessionStorageEvaluator接口,然后进行如下配置:

    [main]
    ...
    sessionStorageEvaluator = com.mycompany.shiro.subject.mgt.MySessionStorageEvaluator
    securityManager.subjectDAO.sessionStorageEvaluator = $sessionStorageEvaluator
    ...
    

    4.6 Web Applications
    通常web应用希望每个请求可以容易地启用或禁用会话的创建,而不管哪个Subject 正在执行请求。这在支持REST,消息服务或RMI构架上起到很好的效果。比如,也许正常的终端用户允许创建和使用session,但是远程的API客户端使用REST或SOAP,没有session的概念。为了支持这种混合的/每请求的功能,Shiro将noSessionCreation过滤器添加到了默认的过滤器池中。这个过滤器可以防止新session的创建,以达到无状态的调用。在INI文件的[urls]部分,显式的在其他过滤器前定义,以确保session不被使用:

    [urls]
    ...
    /rest/** = noSessionCreation, authcBasic, ...
    

    这个过滤器允许session使用当前的session,但不允许在过滤器请求范围内创建新session。也就是说,在一个没有存在session的request或subject内,下面任意四个方法的调用都将自动触发DisabledSessionException:
    a. httpServletRequest.getSession()
    b. httpServletRequest.getSession(true)
    c. subject.getSession()
    d. subject.getSession(true)
    如果一个Subject在访问由noSessionCreation保护的url前已经有session存在,上面四个方法将正常执行。最后,在所有情况下,下面这两个方法都可以执行:
    a. httpServletRequest.getSession(false)
    b. subject.getSession(false)

     

    除特别注明外,本站所有文章均为String me = "Creater\忠实的资深Linux玩家";原创,转载请注明出处来自http://unix8.net/home.php/4043.html

    关于

    发表评论

    暂无评论

    切换注册

    登录

    忘记密码 ?

    切换登录

    注册

    扫一扫二维码分享