Shiro Security是非常不錯的Security框架
最近在我的項目中進行相關整合, shiro不難, 難就難在如何對已經成熟的系統進行整合
作為相關切入點, 我也考慮了很久, 整體運用上了如張開濤大佬所說
對於Subject我們一般這麼使用:
1、身份驗證(login)
2、授權(hasRole*/isPermitted*或checkRole*/checkPermission*)
3、將相應的資料存儲到會話(Session)
4、切換身份(RunAs)/多執行緒身份傳播
5、退出
回歸標題, 正常整合過後, 基本可以正確的進行登錄與登出
那麼開始進行細節休整
大體介紹我們的系統架構是springmvc進行開發, 一個專案裡分出了兩套系統, 系統與系統間的區分僅僅只是 通過url路徑上的不同, 來表現。 那麼現在就出現了一種情況,
表面上視乎能在登陸上做控制, 比如login的時候通過許可權判斷就可以做到。 那麼這時候考慮的是如果用戶之間通過url強行進入呢。
比如系統1使用者登錄, 直接修改url進入系統2。 此時屬於非法訪問。
正常我們會在filter內做過濾, 也好做, 在相關登錄邏輯內對session賦予標記, 在filter做過濾就over了, 不符合直接logout。
想到這裡, 作為一個shiro框架使用者是不是感覺shiro貌似沒起作用。
於是思考一個比較合理的方案, 於是決定當使用者登陸系統時, 對sessionid進行賦值, 系統1則在sessionid前+上相關字串標記session為系統1使用者。 系統2則在sessionid前+上相關字串標記session為系統2使用者。
需求清晰, 那麼進行可行性分。
那麼結合shiro中的session管理, 開始做起了調查。
首先進行是的shiro如何修改sessionid, 這能找卻不能滿足我的需求。 因為市面上的他們都是以單系統, 或者雙專案雙系統進行寫的。 完全不能符合我想要的。
一般都是這麼做的
上面的解決方案是對不同專案進行不同的jsessionid名的配置
但我一個專案裡怎麼可能出現兩個shiro, 不符合我的要求
於是考慮shiro內置處理session我要做點手腳。
首先是查到了sessionid生成器
自訂了一個id生成器
public class SysSessionIdGenerator implements SessionIdGenerator {@Overridepublic Serializable generateId(Session session) {if(session.getAttribute("sysType")!=null){return session.getAttribute("sysType").toString+"_"+UUID.randomUUID.toString;}return UUID.randomUUID.toString;}}主要實現SessionIdGenerator generateId的方法。 這裡可以看見我吧sysType加到uuid前面。
然後就是注入給shiro使用
配置需要加入
sessionDAO 也要記得注入給 sessionManager 這裡我就不寫了
那麼問題又來了, 邏輯上不上應該生成session的時候調用嗎, 那麼shiro的session是在什麼時候生成的呢。
我翻了翻源碼
subject.getSession
這個get方法實現的
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; }可以看出, 當你不傳參數的時候默認進行調用ture,
簡單的 說當getsession(true)時, 會判斷現在是否有session, 如果沒有, 則新生成一個, 有則就用現有的。 false則是如果沒有, 就不生成了返回null。
發現一個問題, 當我剛剛進入登入頁面的時候, 此時shiro已經生成了一個session, 於是在登陸校驗時候不會生成新的session了。 於是考慮了各種辦法, 比如登入的時候先logout一下等等, 當然這些都叫做歪門邪道。 後來發現了這麼一篇文章
我用了他的方法反正是沒成功, 系統還變的有點混亂。
仔細一看他的文章中有這麼一段:
使用過程中發現Shiro在登錄之後不會生成新的Jessionid。 這顯然會出現Session_Fixation。
Shiro自己說會在下一個版本1.3 fix這個問題。
我shiro起步是張開濤大大文章裡的版本,
先換了再說。
發現確實登陸之前與之後sessionid變了, 看來在1.3.2的時候會在getsession重新獲得session。
但是這一點我並不明確, 只能推測是這樣。
但是這還不是我的正道。 重新明確技術細節, 發現我需要重載getsession方法, 在getsession的時候把sysTpye(系統標記)字串傳遞進去。
還是剛剛上面的代碼有這麼一行
SessionContext sessionContext = createSessionContext; Session session = this.securityManager.start(sessionContext);它吧sessionContext傳遞進去創建了。 那麼我似乎可以在這裡做文章, 查閱資料後發現SessionContext繼承了Map。 那麼我就可以直接對它進行put了。
那麼繼續往下挖掘源碼。
這篇挖掘的文章可以看看, 我反正看完思路清晰了一點, 畢竟自己debug比較混亂。
此時考慮到sessionContext物件還不是最終目標session, 那麼我賦予的值要麼shiro會對其進行全部輸出到session裡,
最後扒到這裡, 它只是取了host 然後賦值, 生成simplesession物件。
看來這裡是SessionContext的資料終點, 那麼我systpye也得在這裡進行操作了。
查詢 SessionFactory相關資料後, 發現原來我們自己也可以定義自己的SessionFactory物件。 於是自訂了一個SessionFactory
public class HrsystemSessionFactory implements SessionFactory {@Overridepublic Session createSession(SessionContext initData) {Session session = null;if (initData != null) { String host = initData.getHost; if (host != null) { session = new SimpleSession(host); } if(initData.get("sysType")!=null){ session.setAttribute("sysType", initData.get("sysType")); } }else{ session = new SimpleSession; }return session;}}這裡做的是把sysType的值賦值給session, 然後設定檔注入。
那麼與剛剛的SysSessionIdGenerator對接上了。
接下來就是重頭戲, 重載getsession;以上都有廢話之嫌, 長話短說。
首先拓展suject介面
public interface SysSubject extends Subject { Session getSession(String sysType);}這裡我直接繼承Subject介面;
public static Subject getSubject { Subject subject = ThreadContext.getSubject; if (subject == null) { subject = (new Subject.Builder).buildSubject; ThreadContext.bind(subject); } return subject; }Subject實例是使用ThreadLocal模式來獲取, 若沒有則創建一個並綁定到當前執行緒。 此時創建使用的是Subject內部類Builder來創建的, Builder會創建一個SubjectContext介面的實例DefaultSubjectContext, 最終會委託securityManager來根據SubjectContext資訊來創建一個Subject
上面代碼就是前面介紹源碼的文章裡有講的。
那麼主要的就是需要進行產生實體編寫了。
這裡有點回到原點了,從創建session變成了如何創建subject物件。
但是仔細剖析方法結構,會發現其實subject獲取模式與session獲取模式是一樣的。
但是重寫subjectFactory在網路與張開濤裡面都沒有觸及或者很少。這可能需要閱讀源碼才理解。
代碼如下
public class HrsystemSubjectFactory extends DefaultWebSubjectFactory {public HrsystemSubjectFactory {super; } public Subject createSubject(SubjectContext context) { if (!(context instanceof WebSubjectContext)) { return super.createSubject(context); } WebSubjectContext wsc = (WebSubjectContext) context; SecurityManager securityManager = wsc.resolveSecurityManager; Session session = wsc.resolveSession; boolean sessionEnabled = wsc.isSessionCreationEnabled; PrincipalCollection principals = wsc.resolvePrincipals; boolean authenticated = wsc.resolveAuthenticated; String host = wsc.resolveHost; ServletRequest request = wsc.resolveServletRequest; ServletResponse response = wsc.resolveServletResponse; return new HrsystemSubject(principals, authenticated, host, session, sessionEnabled, request, response, securityManager); } /** * @deprecated since 1.2 - override {@link #createSubject(org.apache.shiro.subject.SubjectContext)} directly if you * need to instantiate a custom {@link Subject} class. */ @Deprecated protected Subject newSubjectInstance(PrincipalCollection principals, boolean authenticated, String host, Session session, ServletRequest request, ServletResponse response, SecurityManager securityManager) { return new WebDelegatingSubject(principals, authenticated, host, session, true, request, response, securityManager); }}主要是在createSubject方法中產生實體HrsystemSubject物件將它傳遞出去。
而這個HrsystemSubject產生實體SysSubject介面 與繼承WebDelegatingSubject物件
public class HrsystemSubject extends WebDelegatingSubject implements SysSubject{ public HrsystemSubject(PrincipalCollection principals, boolean authenticated, String host, Session session, boolean sessionEnabled, ServletRequest request, ServletResponse response, SecurityManager securityManager) { super(principals, authenticated, host, session, sessionEnabled, request, response, securityManager); }public Session getSession(String type) { SessionContext sessionContext = createSessionContext; sessionContext.put("sysType", type); Session session = this.securityManager.start(sessionContext); super.session = decorate(session); return super.session; }}ok那麼此時還沒完,我們需要把HrsystemSubjectFactory注入到shiro中去。
這裡我完全是模仿著session套路感覺注入的了,因為並沒有文章這麼做。(或者我沒看到吧)
於是此時SecurityUtils.getSubject;get的出來的物件就是我們的HrsystemSubject了。
但是這裡運用了下父子繼承原理,get物件實際是Subject,內建物件的實例其實是HrsystemSubject
那麼我們在對其強制裝換成我們剛剛定的(SysSubject)SecurityUtils.getSubject;
於是關於getsession的重載就完成了。
那麼邏輯上輸入getsession(string sysType)那麼就可以對sessionid進行我想要的值了。也能做邏輯控制了。運用場景還是挺廣的。
這裡有點回到原點了,從創建session變成了如何創建subject物件。
但是仔細剖析方法結構,會發現其實subject獲取模式與session獲取模式是一樣的。
但是重寫subjectFactory在網路與張開濤裡面都沒有觸及或者很少。這可能需要閱讀源碼才理解。
代碼如下
public class HrsystemSubjectFactory extends DefaultWebSubjectFactory {public HrsystemSubjectFactory {super; } public Subject createSubject(SubjectContext context) { if (!(context instanceof WebSubjectContext)) { return super.createSubject(context); } WebSubjectContext wsc = (WebSubjectContext) context; SecurityManager securityManager = wsc.resolveSecurityManager; Session session = wsc.resolveSession; boolean sessionEnabled = wsc.isSessionCreationEnabled; PrincipalCollection principals = wsc.resolvePrincipals; boolean authenticated = wsc.resolveAuthenticated; String host = wsc.resolveHost; ServletRequest request = wsc.resolveServletRequest; ServletResponse response = wsc.resolveServletResponse; return new HrsystemSubject(principals, authenticated, host, session, sessionEnabled, request, response, securityManager); } /** * @deprecated since 1.2 - override {@link #createSubject(org.apache.shiro.subject.SubjectContext)} directly if you * need to instantiate a custom {@link Subject} class. */ @Deprecated protected Subject newSubjectInstance(PrincipalCollection principals, boolean authenticated, String host, Session session, ServletRequest request, ServletResponse response, SecurityManager securityManager) { return new WebDelegatingSubject(principals, authenticated, host, session, true, request, response, securityManager); }}主要是在createSubject方法中產生實體HrsystemSubject物件將它傳遞出去。
而這個HrsystemSubject產生實體SysSubject介面 與繼承WebDelegatingSubject物件
public class HrsystemSubject extends WebDelegatingSubject implements SysSubject{ public HrsystemSubject(PrincipalCollection principals, boolean authenticated, String host, Session session, boolean sessionEnabled, ServletRequest request, ServletResponse response, SecurityManager securityManager) { super(principals, authenticated, host, session, sessionEnabled, request, response, securityManager); }public Session getSession(String type) { SessionContext sessionContext = createSessionContext; sessionContext.put("sysType", type); Session session = this.securityManager.start(sessionContext); super.session = decorate(session); return super.session; }}ok那麼此時還沒完,我們需要把HrsystemSubjectFactory注入到shiro中去。
這裡我完全是模仿著session套路感覺注入的了,因為並沒有文章這麼做。(或者我沒看到吧)
於是此時SecurityUtils.getSubject;get的出來的物件就是我們的HrsystemSubject了。
但是這裡運用了下父子繼承原理,get物件實際是Subject,內建物件的實例其實是HrsystemSubject
那麼我們在對其強制裝換成我們剛剛定的(SysSubject)SecurityUtils.getSubject;
於是關於getsession的重載就完成了。
那麼邏輯上輸入getsession(string sysType)那麼就可以對sessionid進行我想要的值了。也能做邏輯控制了。運用場景還是挺廣的。