最近做了一個項目,要用到shiro,做完之后發現有個異常經常發生org.apache.shiro.session.UnknownSessionException: There is no session with id?,經過多天的研究,終于得以解決
登錄的時候異常信息:
- org.apache.shiro.session.UnknownSessionException:?There?is?no?session?with?id?[4e8fe40a-6347-4c53-b273-829889656f6e]??
- ????at?org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:170)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSessionFromDataSource(DefaultSessionManager.java:236)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSession(DefaultSessionManager.java:222)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.session.mgt.AbstractValidatingSessionManager.doGetSession(AbstractValidatingSessionManager.java:118)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.session.mgt.AbstractNativeSessionManager.lookupSession(AbstractNativeSessionManager.java:108)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.session.mgt.AbstractNativeSessionManager.lookupRequiredSession(AbstractNativeSessionManager.java:112)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.session.mgt.AbstractNativeSessionManager.getAttribute(AbstractNativeSessionManager.java:209)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.session.mgt.DelegatingSession.getAttribute(DelegatingSession.java:141)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.session.ProxiedSession.getAttribute(ProxiedSession.java:121)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.session.ProxiedSession.getAttribute(ProxiedSession.java:121)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.subject.support.DelegatingSubject.getRunAsPrincipalsStack(DelegatingSubject.java:469)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.subject.support.DelegatingSubject.getPrincipals(DelegatingSubject.java:153)[251:org.apache.shiro.core:1.2.3]??
- ????at?org.apache.shiro.subject.support.DelegatingSubject.getPrincipal(DelegatingSubject.java:149)[251:org.apache.shiro.core:1.2.3]??
為何找不到呢,原因是這樣的。
當用戶登錄的時候,web容器tomcat或者jetty會在線程池里面啟用線程調度,線程里面找到對應的servlet,shiro登錄的時候代碼如下
- <span?style="white-space:pre">????</span>Subject?currentUser?=?SecurityUtils.getSubject();??
- ??????????
- ????????String?sessionId?=?"";??
- ??
- ????????if?(!currentUser.isAuthenticated())?{??
- ??????????????UsernamePasswordToken?token?=?new?UsernamePasswordToken(username,?DigestUtils.md5Hex(passwd));??
- ??????????????token.setRememberMe(true);??
- ??????????????currentUser.login(token);??
- ?????????}??
方法SecurityUtils.getSubject()源碼是這樣
- public?static?Subject?getSubject()?{??
- ????????Subject?subject?=?ThreadContext.getSubject();??
- ????????if?(subject?==?null)?{??
- ????????????subject?=?(new?Subject.Builder()).buildSubject();??
- ????????????ThreadContext.bind(subject);??
- ????????}??
- ????????return?subject;??
- }??
我們清楚的看到subject是從ThreadContext獲取,創建過就直接從里面獲取,沒有創建的話就重新創建一個subject,然后綁定到ThreadContext,調用subject獲取session的時候,他會去創建一個session,并且把session緩存起來,操作方法是在AbstractSessionDAO類里面,跟蹤得知這里放的是subject的代理對象。如果session超時時間設置過短的話,在用戶登錄的時候,隨著web容器分配的線程,很大的機會會分配之前的線程,而之前的線程綁定過了subject,subject沒有失效,subejct對象里面的session也沒有什么問題,但是session緩存里面的session失效了,用戶登錄的時候執行到currentuser.login(token)這個方法,他拿著之前的session,那后要去緩存里面讀取,但是已經失效了,所以會報上面那個異常。
問題就是出現在這里,subject綁定到thread上下文里面,subject對象的session是個代理對象,正真的session是放在緩存里面,web容器隨機分配的線程有可能綁定過subject,一旦session失效,就會報錯。
解決的辦法是在shiro去讀取session之前判斷有沒有失效,如果失效移除ThreadContext里面的subject,并且刪除緩存里面的session,代碼如下
- @Override??
- ????public?String?login(String?username,?String?passwd)?{??
- ??????????
- ????????Subject?currentUser?=?SecurityUtils.getSubject();??
- ??????????
- ??
- ????????if((System.currentTimeMillis()-currentUser.getSession().getStartTimestamp().getTime())>=lengthenTimeOut-1000){??
- ????????????ThreadContext.remove(ThreadContext.SUBJECT_KEY);??
- ????????????shiroSessionManager.getSessionDAO().delete(currentUser.getSession());??
- ????????????currentUser?=?SecurityUtils.getSubject();??
- ????????}??
- ??????????
- ????????String?sessionId?=?"";??
- ????try?{??
- ????????if?(!currentUser.isAuthenticated())?{??
- ????????????UsernamePasswordToken?token?=?new?UsernamePasswordToken(username,?DigestUtils.md5Hex(passwd));??
- ????????????token.setRememberMe(true);??
- ????????????currentUser.login(token);??
- ????????}??
- ?????????
- ????????sessionId?=?currentUser.getSession().getId().toString();??
- ??????????
- ??
- ????}?catch?(ExcessiveAttemptsException?ex)?{??
- ????????log.info(username?+?"帳號被鎖定1小時!",?ex);??
- ????}?catch?(UnknownAccountException?uae)?{??
- ????????log.info(username?+?"賬戶不存在!",?uae);??
- ????}?catch?(IncorrectCredentialsException?ice)?{??
- ????????log.info(username?+?"密碼不正確!",?ice);??
- ????}?catch?(LockedAccountException?lae)?{??
- ????????log.info(username?+?"賬戶被禁了!",?lae);??
- ????}?catch?(AuthenticationException?ae)?{??
- ????????log.info(username?+?"用戶名或密碼錯誤!",?ae);??
- ????}?catch?(UnknownSessionException?ue)?{??
- ????????log.info("登錄session失效"?+?sessionId,?ue);??
- ??????????
- ????}??
- ??
- ????log.info("登錄成功返回的sessionId+++++++++++++"?+?sessionId);??
- ????return?sessionId;??
- }??