国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

Shiro

2019-11-14 22:05:20
字體:
來源:轉載
供稿:網友
Shiro - 關于sessionShiro Session

session管理可以說是Shiro的一大賣點。

Shiro可以為任何應用(從簡單的命令行程序還是手機應用再到大型企業應用)提供會話解決方案。

在Shiro出現之前,如果我們想讓你的應用支持session,我們通常會依賴web容器或者使用EJB的Session Bean。

Shiro對session的支持更加易用,而且他可以在任何應用、任何容器中使用。

即便我們使用Servlet或者EJB也并不代表我們必須使用容器的session,Shiro提供的一些特性足以讓我們用Shiro session替代他們。

  • 基于POJO
  • 易定制session持久化
  • 容器無關的session集群
  • 支持多種客戶端訪問
  • 會話事件監聽
  • 對失效session的延長
  • 對Web的透明支持
  • 支持SSO

使用Shiro session時,無論是在javaSE還是web,方法都是一樣的。

public static void main(String[] args) {    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro/shiro.ini");    SecurityUtils.setSecurityManager(factory.getInstance());    Subject currentUser = SecurityUtils.getSubject();    UsernamePassWordToken token = new UsernamePasswordToken("king","t;stmdtkg");    currentUser.login(token);    Session session = currentUser.getSession();    System.out.PRintln(session.getHost());    System.out.println(session.getId());    System.out.println(session.getStartTimestamp());    System.out.println(session.getLastaccessTime());    session.touch();    User u = new User();     session.setAttribute(u, "King.");    Iterator<Object> keyItr = session.getAttributeKeys().iterator();    while(keyItr.hasNext()){        System.out.println(session.getAttribute(keyItr.next()));    }}

無論是什么環境,只需要調用Subject的getSession()即可。

另外Subject還提供了一個...

Session getSession(boolean create);

即,當前Subject的session不存在時是否創建并返回新的session。

以DelegatingSubject為例:(注意!從Shiro 1.2開始多了一個isSessionCreationEnabled屬性,其默認值為true。)

public Session getSession() {    return getSession(true);}public Session getSession(boolean create) {    if (log.isTraceEnabled()) {        log.trace("attempting to get session; create = " + create +                "; session is null = " + (this.session == null) +                "; session has id = " + (this.session != null && session.getId() != null));    }    if (this.session == null && create) {        //added in 1.2:        if (!isSessionCreationEnabled()) {            String msg = "Session creation has been disabled for the current subject.  This exception indicates " +                    "that there is either a programming error (using a session when it should never be " +                    "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +                    "for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +                    "for more.";            throw new DisabledSessionException(msg);        }        log.trace("Starting session for host {}", getHost());        SessionContext sessionContext = createSessionContext();        Session session = this.securityManager.start(sessionContext);        this.session = decorate(session);    }    return this.session;}

SessionManager

正如其名,sessionManager用于為應用中的Subject管理session,比如創建、刪除、失效或者驗證等。

和Shiro中的其他核心組件一樣,他由SecurityManager維護。

(注意:public interface SecurityManager extends Authenticator, Authorizer, SessionManager)。

public interface SessionManager {    Session start(SessionContext context);    Session getSession(SessionKey key) throws SessionException;}

Shiro為SessionManager提供了3個實現類(順便也整理一下與SecurityManager實現類的關系)。

  • DefaultSessionManager
  • DefaultWebSessionManager
  • ServletContainerSessionManager

其中ServletContainerSessionManager只適用于servlet容器中,如果需要支持多種客戶端訪問,則應該使用DefaultWebSessionManager。

默認情況下,sessionManager的實現類的超時設為30分鐘。

見AbstractSessionManager:

public static final long DEFAULT_GLOBAL_SESSION_TIMEOUT = 30 * MILLIS_PER_MINUTE;private long globalSessionTimeout = DEFAULT_GLOBAL_SESSION_TIMEOUT;

當然,我們也可以直接設置AbstractSessionManager的globalSessionTimeout。

比如在.ini中:

securityManager.sessionManager.globalSessionTimeout = 3600000

注意!如果使用的SessionManager是ServletContainerSessionManager(沒有繼承AbstractSessionManager),超時設置則依賴于Servlet容器的設置。

見: https://issues.apache.org/jira/browse/SHIRO-240

session過期的驗證方法可以參考SimpleSession:

protected boolean isTimedOut() {    if (isExpired()) {        return true;    }    long timeout = getTimeout();    if (timeout >= 0l) {        Date lastAccessTime = getLastAccessTime();        if (lastAccessTime == null) {            String msg = "session.lastAccessTime for session with id [" +                    getId() + "] is null.  This value must be set at " +                    "least once, preferably at least upon instantiation.  Please check the " +                    getClass().getName() + " implementation and ensure " +                    "this value will be set (perhaps in the constructor?)";            throw new IllegalStateException(msg);        }        // Calculate at what time a session would have been last accessed        // for it to be expired at this point.  In other words, subtract        // from the current time the amount of time that a session can        // be inactive before expiring.  If the session was last accessed        // before this time, it is expired.        long expireTimeMillis = System.currentTimeMillis() - timeout;        Date expireTime = new Date(expireTimeMillis);        return lastAccessTime.before(expireTime);    } else {        if (log.isTraceEnabled()) {            log.trace("No timeout for session with id [" + getId() +                    "].  Session is not considered expired.");        }    }    return false;}

試著從SecurityUtils.getSubject()一步步detect,感受一下session是如何設置到subject中的。判斷線程context中是否存在Subject后,若不存在,我們使用Subject的內部類Builder進行buildSubject();

public static Subject getSubject() {    Subject subject = ThreadContext.getSubject();    if (subject == null) {        subject = (new Subject.Builder()).buildSubject();        ThreadContext.bind(subject);    }    return subject;}

buildSubject()將建立Subject的工作委托給securityManager.createSubject(subjectContext)

createSubject會調用resolveSession處理session。

protected SubjectContext resolveSession(SubjectContext context) {    if (context.resolveSession() != null) {        log.debug("Context already contains a session.  Returning.");        return context;    }    try {        //Context couldn't resolve it directly, let's see if we can since we have direct access to         //the session manager:        Session session = resolveContextSession(context);        if (session != null) {            context.setSession(session);        }    } catch (InvalidSessionException e) {        log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous " +                "(session-less) Subject instance.", e);    }    return context;}

resolveSession(subjectContext),首先嘗試從context(MapContext)中獲取session,如果無法直接獲取則改為獲取subject,再調用其getSession(false)。

如果仍不存在則調用resolveContextSession(subjectContext),試著從MapContext中獲取sessionId。

根據sessionId實例化一個SessionKey對象,并通過SessionKey實例獲取session。

getSession(key)的任務直接交給sessionManager來執行。

public Session getSession(SessionKey key) throws SessionException {    return this.sessionManager.getSession(key);}

sessionManager.getSession(key)方法在AbstractNativeSessionManager中定義,該方法調用lookupSession(key)

lookupSession調用doGetSession(key)doGetSession(key)是個protected abstract,實現由子類AbstractValidatingSessionManager提供。

doGetSession調用retrieveSession(key),該方法嘗試通過sessionDAO獲得session信息。

最后,判斷session是否為空后對其進行驗證(參考SimpleSession.validate())。

protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {    enableSessionValidationIfNecessary();    log.trace("Attempting to retrieve session with key {}", key);    Session s = retrieveSession(key);    if (s != null) {        validate(s, key);    }    return s;}

Session Listener

我們可以通過SessionListener接口或者SessionListenerAdapter來進行session監聽,在session創建、停止、過期時按需進行操作。

public interface SessionListener {    void onStart(Session session);    void onStop(Session session);    void onExpiration(Session session);}

我只需要定義一個Listener并將它注入到sessionManager中。

package pac.testcase.shiro.listener;import org.apache.shiro.session.Session;import org.apache.shiro.session.SessionListener;public class MySessionListener implements SessionListener {    public void onStart(Session session) {        System.out.println(session.getId()+" start...");    }    public void onStop(Session session) {        System.out.println(session.getId()+" stop...");    }    public void onExpiration(Session session) {        System.out.println(session.getId()+" expired...");    }}

[main]realm0=pac.testcase.shiro.realm.MyRealm0realm1=pac.testcase.shiro.realm.MyRealm1authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategysessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager#sessionManager = org.apache.shiro.web.session.mgt.ServletContainerSessionManagersessionListener = pac.testcase.shiro.listener.MySessionListenersecurityManager.realms=$realm1securityManager.authenticator.authenticationStrategy = $authcStrategysecurityManager.sessionManager=$sessionManager#sessionManager.sessionListeners =$sessionListener  securityManager.sessionManager.sessionListeners=$sessionListener

SessionDAO

SessionManager將session CRUD的工作委托給SessionDAO。

我們可以用特定的數據源API實現SessionDAO,以將session存儲于任何一種數據源中。

public interface SessionDAO {    Serializable create(Session session);    Session readSession(Serializable sessionId) throws UnknownSessionException;    void update(Session session) throws UnknownSessionException;    void delete(Session session);    Collection<Session> getActiveSessions();}

當然,也可以把子類拿過去用。

  • AbstractSessionDAO:在create和read時對session做驗證,保證session可用,并提供了sessionId的生成方法。
  • CachingSessionDAO:為session存儲提供透明的緩存支持,使用CacheManager維護緩存。
  • EnterpriseCacheSessionDAO:通過匿名內部類重寫了AbstractCacheManager的createCache,返回MapCache對象。
  • MemorySessionDAO:基于內存的實現,所有會話放在內存中。

下圖中的匿名內部類就是EnterpriseCacheSessionDAO的CacheManager。默認使用MemorySessionDAO(注意!DefaultWebSessionManager extends DefaultSessionManager)

當然,我們也可以試著使用緩存。

Shiro沒有默認啟用EHCache,但是為了保證session不會在運行時莫名其妙地丟失,建議啟用EHCache優化session管理。

啟用EHCache為session持久化服務非常簡單,首先我們需要添加一個denpendency。

<dependency>    <groupId>org.apache.shiro</groupId>    <artifactId>shiro-ehcache</artifactId>    <version>${shiro.version}</version></dependency>

接著只需要配置一下,以.ini配置為例:

[main]realm0=pac.testcase.shiro.realm.MyRealm0realm1=pac.testcase.shiro.realm.MyRealm1authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategysessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManagercacheManager=org.apache.shiro.cache.ehcache.EhCacheManagersessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO#sessionManager = org.apache.shiro.web.session.mgt.ServletContainerSessionManagersessionListener = pac.testcase.shiro.listener.MySessionListenersecurityManager.realms=$realm1securityManager.authenticator.authenticationStrategy = $authcStrategysecurityManager.sessionManager=$sessionManagersessionManager.sessionListeners =$sessionListenersessionDAO.cacheManager=$cacheManager  securityManager.sessionManager.sessionDAO=$sessionDAOsecurityManager.sessionManager.sessionListeners=$sessionListener

此處主要是cacheManager的定義和引用。

另外,此處使用的sessionDAO為EnterpriseCacheSessionDAO。

前面說過EnterpriseCacheSessionDAO使用的CacheManager是基于MapCache的。

其實這樣設置并不會影響,因為EnterpriseCacheSessionDAO繼承CachingSessionDAO,CachingSessionDAO實現CacheManagerAware。

注意!只有在使用SessionManager的實現類時才有sessionDAO屬性。

(事實上他們把sessionDAO定義在DefaultSessionManager中了,但似乎有將sessionDAO放到AbstractValidatingSessionManager的打算。)

如果你在web應用中配置Shiro,啟動后你會驚訝地發現securityManger的sessionManager屬性居然是ServletContainerSessionManager。

看一下上面的層次圖發現ServletContainerSessionManager和DefaultSessionManager沒有關系。

也就是說ServletContainerSessionManager不支持SessionDAO(cacheManger屬性定義在CachingSessionDAO)。

此時需要顯示指定sessionManager為DefaultWebSessionManager。

關于EhCache的配置,默認情況下EhCacheManager使用指定的配置文件,即:

private String cacheManagerConfigFile = "classpath:org/apache/shiro/cache/ehcache/ehcache.xml";

來看一下他的配置:

<ehcache>    <diskStore path="java.io.tmpdir/shiro-ehcache"/>    <defaultCache            maxElementsInMemory="10000"            eternal="false"            timeToIdleSeconds="120"            timeToLiveSeconds="120"            overflowToDisk="false"            diskPersistent="false"            diskExpiryThreadIntervalSeconds="120"            />    <cache name="shiro-activeSessionCache"           maxElementsInMemory="10000"           overflowToDisk="true"           eternal="true"           timeToLiveSeconds="0"           timeToIdleSeconds="0"           diskPersistent="true"           diskExpiryThreadIntervalSeconds="600"/>    <cache name="org.apache.shiro.realm.text.PropertiesRealm-0-accounts"           maxElementsInMemory="1000"           eternal="true"           overflowToDisk="true"/></ehcache>

如果打算改變該原有設置,其中有兩個屬性需要特別注意:

  • overflowToDisk="true":保證session不會丟失。
  • eternal="true":保證session緩存不會被自動失效,將其設為false可能會和session validation的邏輯不符。另外,name默認使用"shiro-activeSessionCache"

    public static final String ACTIVESESSIONCACHE_NAME = "shiro-activeSessionCache";

如果打算使用其他名字,只要在CachingSessionDAO或其子類設置activeSessionsCacheName即可。

當創建一個新的session時,SessionDAO的實現類使用SessionIdGenerator來為session生成ID。

默認使用的SessionIdGenerator是JavaUuidSessionIdGenerator,其實現為:

public Serializable generateId(Session session) {    return UUID.randomUUID().toString();}

當然,我們也可以自己定制實現SessionIdGenerator。

Session Validation & Scheduling

比如說用戶在瀏覽器上使用web應用時session被創建并緩存什么的都沒有什么問題,只是用戶退出的時候可以直接關掉瀏覽器、關掉電源、停電或者其他天災什么的。

然后session的狀態就不得而知了(it is orphaned)。

為了防止垃圾被一點點堆積起來,我們需要周期性地檢查session并在必要時刪除session。

于是我們有SessionValidationScheduler:

public interface SessionValidationScheduler {    boolean isEnabled();    void enableSessionValidation();    void disableSessionValidation();}

Shiro只提供了一個實現,ExecutorServiceSessionValidationScheduler。 默認情況下,驗證周期為60分鐘。

當然,我們也可以通過修改他的interval屬性改變驗證周期(單位為毫秒),比如這樣:

sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationSchedulersessionValidationScheduler.interval = 3600000securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler

如果打算禁用按周期驗證session(比如我們在Shiro外做了一些工作),則可以設置

securityManager.sessionManager.sessionValidationSchedulerEnabled = false

如果不打算刪除失效的session(比如我們要做點統計之類的),則可以設置

securityManager.sessionManager.deleteInvalidSessions = false

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 武强县| 华阴市| 松潘县| 赤水市| 武宣县| 通江县| 正安县| 灵寿县| 秭归县| 平利县| 怀来县| 余江县| 德庆县| 罗田县| 峨眉山市| 陈巴尔虎旗| 安多县| 内江市| 崇左市| 正定县| 眉山市| 遂昌县| 东乌| 达州市| 泸水县| 巩留县| 鹤山市| 临西县| 平舆县| 若尔盖县| 罗定市| 平昌县| 江门市| 宜昌市| 舟山市| 舞钢市| 肇源县| 张家川| 乌拉特前旗| 望谟县| 扎兰屯市|