一、項目整體架構分析
首先數據庫中menu_catalog和menu表是存放菜單目錄的,導航欄的展示都是從這里面取,menu中有導航欄的路徑,通過這些路徑以及menu_id從MainController中查找跳轉的共同頁面(frame.jsp)
根據點擊menu_id的不同展示不同的頁面,frame.jsp中包含的頁面有top.jsp(里面主要引入一些公用的js,定義<html><body>的開始標簽),bottom.jsp(里面是</body></html>結束標簽)這兩個定義在頁面的最前面和最后面,中間包括header.jsp(里面是頭部導航欄內容,有標題展示的邏輯),footer.jsp(這里面是底部展示的公共部分),main-sidebar.jsp(這個是左側欄展示的內容,以及操作的邏輯),<iframe></iframe>最后是要展示的頁面menuFrame,定義在iframe標簽中,這里設置的最小高度是800px;如果頁面需要還可以自定義高度。
代碼是:
var timer; if (timer) { clearInterval(timer); } timer = setInterval(function () { var menuFrame = $(window.parent.document).find("#menuFrame"); var height = $("#bigDiv").height() + 20; menuFrame.height(height); }, 500);//每0.5秒檢查一次
根據div的高度自動檢測,然后根據div的高度改變menuFrame的高度,這樣不同頁面就可以根據內容需要設置成合適的高度。
在MainController中設置menuId,根據menuId是否選中在header.jsp中設置標題的不同樣式。
點擊導航欄除了首頁,其他都是走menu/{id}的路徑,都會跳轉到frame頁面,也就是左側是導航欄,右側是頁面的樣式,點擊首頁標簽,會跳轉到home頁面。
Window函數:
(1)Window setInterval() 方法:可以重復執行
setInterval() 方法可按照指定的周期(以毫秒計)來調用函數或計算表達式。
setInterval() 方法會不停地調用函數,直到 clearInterval() 被調用或窗口被關閉。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的參數。
(2)Window setTimeout() 方法:只執行一次
setTimeout() 方法用於在指定的毫秒數后調用函數或計算表達式。
使用 clearTimeout() 方法來阻止函數的執行。
二、與服務器交互獲取數據
個人理解:用戶進行登錄操作,向服務器發送http請求,創建一個到服務器指定端口的TCP連接,HTTP服務器則在那個端口監聽客戶端的請求, 服務器根據請求信息做出響應,響應完之后釋放TCP連接。
向服務器發送請求包括:用戶在瀏覽器中輸入地址,或者使用AJAX向服務器請求數據。
HTTP協議是無狀態的,無法保存用戶的登錄狀態,也就是用戶登錄網站從一個頁面跳轉到另一個頁面不會保存登錄狀態,為了實現保存狀態功能,引入了COOKIE技術,HTTP+COOKIE這樣就可以保存用戶狀態。
三、項目整合shiro過程分析
項目中,用戶成功登錄之后就會 跳轉到首頁,默認配置是在shiro過濾器中設置,如果我們是直接訪問登錄頁面的話,shiro就會根據我們配置的successUrl去重定向,如果我們沒有配置successUrl的話,那么shiro重定向默認的/。
用戶輸入用戶名密碼點擊登錄會被FormAuthenticationFilter攔截器攔截,同樣,當點擊登錄的時候自定義Controller就會調用login方法。
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
1、Controller中我們使用subject.login(token)來執行登陸操作
2、DelegatingSubject.login中使用securityManager.login(this.token)
3、DefaultSecrityManager(這里我注入的是這個secrityManager,也可以自定義).login中使用authenticate(token)
4、AuthenticatingSecurityManager.authenticate中使用authenticator.authenticate(token)
5、AbstractAuthenticator.authenticate中使用doAuthenticate(token)
6、ModularRealmAuthenticator.doAuthenticate中通過getRealms來得到所有的Realm,然后使用(我假設這里只定義了一個realm)doSingleRealmAuthentication(realm, authenticationtoekn)
7、ModularReamlmAuthenticator.doSingleRealmAuthentication中使用了Realm.getAuthenticationInfo(token)
8、AUthenticatingRealm中使用doGetAuthenticatingInfor(token),這個方法其實就是我們自定的Realm中的方法,而后使用assertCredentialsMatch(token,info)
9、自定義或者默認的CredentialMatcher的doCredentialsMatch方法對info中的Credential和token中的crediential進行比對
總結:用戶點擊登錄,調用login方法,對用戶信息進行認證,認證的是自定義的realm,即前端輸入的用戶信息與數據庫中查詢到的用戶信息進行對比,如果一致認證成功,用戶認證成功之后獲取該用戶的權限,根據不同用戶展示不同信息。
shiro中配置與過濾器(表單過濾器):https://www.cnblogs.com/jtlgb/p/9577207.html
authc對應shiro中的攔截器FormAuthenticationFilter,驗證之后才能訪問。
項目中獲取用戶登錄對象、獲取session、設置session、刪除session、用戶注銷都是在com.cattsoft.system.security下的SecurityManager類中設置的。
shiro登錄過程驗證:
shiro登錄之后邏輯跑通:https://blog.csdn.net/u011607686/article/details/80360744
登錄過程總結:由於shiro默認注冊了FormAuthenticationFilter,所以配置中可以不需要為此方法定義bean,但有個前提,登錄頁面中的登錄賬號和密碼,記住我的name必須和FormAuthenticationFilter默認的名稱一致。登錄頁面提交后跳轉到/login,進入登錄方法,由於此路徑權限設置為authc,shiro對該路徑進行過濾,authc權限由FormAuthenticationFilter進行過濾,登錄請求進入onAccessDenied方法
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if (isLoginRequest(request, response)) { //判斷是否是登錄請求 if (isLoginSubmission(request, response)) { // 是否是http post請求 if (log.isTraceEnabled()) { log.trace("Login submission detected. Attempting to execute login."); } return executeLogin(request, response); } else { if (log.isTraceEnabled()) { log.trace("Login page view."); } //allow them to see the login page ;) return true; } } else { if (log.isTraceEnabled()) { log.trace("Attempting to access a path which requires authentication. Forwarding to the " + "Authentication url [" + getLoginUrl() + "]"); } saveRequestAndRedirectToLogin(request, response); return false; } }
其中 executeLogin(request, response)方法的具體實現在繼承的AuthenticatingFilter里,FormAuthenticationFilter過濾器繼承AuthenticatingFilter過濾器,AuthenticatingFilter過濾器繼承AuthenticationFilter過濾器。
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { AuthenticationToken token = createToken(request, response); if (token == null) { String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +"must be created in order to execute a login attempt."; throw new IllegalStateException(msg); } try { Subject subject = getSubject(request, response); subject.login(token); return onLoginSuccess(token, subject, request, response); } catch (AuthenticationException e) { return onLoginFailure(token, e, request, response); } }
剖析:createToken(request, response); 具體實現在子類FormAuthenticationFilter中
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { String username = getUsername(request); String password = getPassword(request); return createToken(username, password, request, response); }
從上可以看出,具體的登錄賬號和密碼從request中取出來,並創建了token對象,調用subject的login方法,login方法實現大致流程是用token去realm中取AuthenticationInfo對象,AuthenticationInfo對象存放的是正確的登錄賬號和密碼,並和token中數據進行匹配,然后根據匹配情況返回相應的結果。
四、shiro中jsp標簽
principal標簽:取值取的是用戶登錄的時候,在realm實現類中
return new SimpleAuthenticationInfo(user,user.getPswd(),getName()); 在new SimpleAuthenticationInfo(第一個參數,....) 的第一個參數放的如果是一個username(用戶名),那么就可以直接用。 <!--取到username--> <shiro: principal/> 如果第一個參數放的是對象,比如放的是User對象。那么如果需要username字段。 <!--需要指定property--> <shiro:principal property="username"/>
參考:https://blog.csdn.net/baidu_37366055/article/details/88072135
五、shiro中鑒權和用戶權限
用戶在登錄的時候在SecurityRealm中會根據登錄用戶查詢數據庫中該用戶所擁有的權限,主要根據operation、permission、operate_permission、menu、menu_operation五張表可以查詢用戶的菜單和目錄權限,當不同用戶登錄時會展示不同的菜單內容,管理員登錄之后可以為每個角色和用戶進行授權操作,也是操作這幾張表,授權之后用戶展示信息會改變。
授權:https://blog.csdn.net/www1056481167/article/details/89061629
項目用戶權限個人理解:用戶登錄的時候從shiro中獲取該用戶菜單和菜單目錄,在鑒權的時候在doGetAuthorizationInfo方法中查詢用戶的權限信息並保存在用戶信息中,什么時候進行鑒權是個問題。
shiro認證和鑒權過程:https://www.cnblogs.com/zhukaixin/p/10722811.html
shiro權限認證及授權的執行流程:https://www.cnblogs.com/JerryTomcat/p/11897554.html
總結:用戶登錄之后跳轉到登錄成功頁面,當頁面中遇到shiro標簽時,這時會執行授權方法,在本項目中,用戶登錄成功跳轉首頁,導航欄的顯示中就有shiro標簽,就會查詢登錄用戶所有的權限。
過濾器繼承關系:https://blog.csdn.net/u014203449/article/details/80689268
六、項目整合shiro新的理解
配置文件中設置了menuFilterChainDefinition,其中屬性有filterChainDefinitions,里面是訪問路徑,其中有/login = authc,也就是當點擊登錄按鈕的時候被攔截器進行攔截,攔截器是自定義的SecurityAuthcFilter,並且繼承FormAuthenticationFilter,也就是說登錄的時候,所有提交的參數還是被表單攔截器攔截,然后根據這些參數進行用戶認證,另外SecurityAuthcFilter中有調用SecurityManager.isSessionUserAdminRole();里面判斷是否有管理員角色,調用這個方法之后就會觸發鑒權方法,即用戶的權限查詢就是在這個時候執行的。
用戶登錄認證和鑒權大體流程:
用戶點擊登錄,被SecurityAuthcFilter攔截器攔截,SecurityAuthcFilter繼承FormAuthenticationFilter,FormAuthenticationFilter繼承AuthenticatingFilter,AuthenticatingFilter會執行executeLogin方法,方法里有subject.login(token);方法,shiro當遇到這個方法時會觸發用戶認證方法,即SecurityRealm中的doGetAuthenticationInfo方法,方法查詢數據庫和參數token進行對比,經過一系列驗證,最后用戶成功登錄,接着執行下面的方法onLoginSuccess,這時會跳轉到SecurityAuthcFilter中的onLoginSuccess方法中,里面有SecurityManager.isSessionUserAdminRole();這段代碼會觸發鑒權方法,即SecurityRealm中的doGetAuthorizationInfo,通過查詢數據庫查獲取到該用戶所擁有的所有權限,然后接着執行下面的代碼。