本文將使用spring,springmvc,mybatis,shiro都是最新版本的框架+JDK1.8完成用戶登錄模塊的DEMO,該DEMO具備較完善的功能,大部分項目都要做權限控制,大部分項目的思路都可以用來參照。本文使用shiro框架來完成認證和授權,同功能的框架還有一種叫做spring security,spring保安的框架,他倆的區別就是一個是Apache旗下的一個是Spring旗下的,具體哪個好你們可以自己去體驗體驗。
shiro是基於過濾器完成權限控制的,所以我們要在web.xml中給shiro配置一個專屬過濾器。
但是這個過濾器是spring提供的,spring通過代理調用 shiro的過濾器,使用spring security框架時,也得這樣配置。url-pattern為/*匹配所有請求,都走這個過濾器。下面我們看一下shiro在spring的配置文件中applicationContext.xml的配置。
這裡需要配置一個bean,此bean的名字必須與web.xml的filter-name相同,spring代理時就是通過<filter-name>shiroFilter</filter-name>找到代理對象的,如果不配置就會拋出error,create bean shiroFilter Class not found 的異常,所以我們需要將shiro的過濾器註冊到spring容器中給spring代理。這裡註冊的是org.apache.shiro.spring.web.ShiroFilterFactoryBean工廠類bean,shiro用的過濾器都是由這個工廠創建,其中有shiro自帶的過濾器,也可以加入自定義的過濾器。我們使用shiro的登錄校驗時就用到了shiro的authc過濾器。該過濾器功能就是檢查session中是否擁有shiro的授權,如果沒有的話就要跳轉到我們上訪在property配置的unauthorizedUrl路徑。
我們註冊完shiroFilter bean之後還需要為其中屬性進行注入,有三個url,一個是登錄用的url,校驗成功url和校驗失敗url。另一個就是securityManager,不要在創建一個SecurityManager bean注入到spring容器中,由於這是一個接口,在啟動spring時會拋出異常,我們需要在class位置填寫他的實現類DefaultWebSecurityManager。那麼他的作用是什麼呢,有點類似dispatcherServlet它是核心方法的調用者,在shiro中處於一種指揮中心似的地位。所以我們需要使用的一些功能都是經由SecurityManager一手操辦的,也是ShiroFilterFactoryBean依賴它的一個原因。然後我們需要配置一個filterChainDefinitions對過濾器鏈的定義。shiro底層是將所有的過濾器封裝到一個linkedHashMap裡面的,我們的url在訪問伺服器時,先經過這個過濾器鏈再到攔截器,最後才到servlet,filterChainDefinitions其實也是一個map,他會根據我們寫的url 來取到過濾器鏈中的相應過濾器來執行。就是說如果寫不同的url-pattern那麼經過的shiro內部過濾器時不同的。shiro的過濾器有以下幾種
anon 這種字母是過濾器的名字縮寫,shiro可以識別,以下就用字母來指代過濾器。anon的功能是一個空的過濾器,它的作用是對靜態資源文件進行放行,比如圖片文件,js,css等都需要使用anon過濾器,也包括登錄頁面,等一些特殊的url,都是可以直接進行訪問。authc 該過濾器會檢查訪問url的session中是否擁有shiro的授權,如果沒有則跳轉至上面配置的校驗失敗頁面。幾乎除了幾個特殊頁面外,所有的url都需要經過這個過濾器,shiro會在用戶登錄成功之後進行授權,所以需要登錄之後才能訪問的url都要經過這個過濾器。roles 是一種角色管理過濾器,它能夠對用戶登錄的帳號的種類進行過濾。被roles匹配的url需要特定的用戶帳號才可以訪問,比如管理員的帳號和普通用戶的帳號是不一樣的。perms 是一種權限過濾器,它對用戶權限進行過濾,它屬於進一步的劃分,並不是所有roles級別都有某個權限。
等等。沒有使用shiro的時候我們都是在login方法中查詢資料庫,那麼看看在使用shiro之後login方法該怎麼寫。一個成熟的項目的登錄頁面現在已經有簡訊登錄,驗證碼登錄以及人臉識別登錄了。這裡使用一種古老的技術用戶名密碼+字母數字組合驗證碼登錄。先看登錄頁面
背景圖片很精美,是我從1688登錄頁下載下來的,看關鍵信息。需要用input提交的有三條數據,用戶名密碼驗證碼和一個登錄submit按鈕。這裡的驗證碼是一個jsp頁面,並不是ajax每隔一段時間刷新那種,也是個非常low的驗證碼。也就是說每次登錄按鈕按下是我們實際提交四條數據,除了頁面上的圖形驗證還有jsp頁面的驗證碼,jsp頁面驗證碼存在session裡。下面看java代碼
該代碼中我們需要做幾件事情:
參數接收 用戶名和密碼使用實體類TUser接收參數接收 手寫驗證碼從Session中拿出jsp頁面的驗證碼
接下來需要對驗證碼做幾個判斷,非空,不是null和不等於空串。如果if為false則重定向至登錄頁面並提示原因。下一步驗證驗證碼輸入是否正確,如果為false重定向並提示。接下來通過SecurityUtils.getSubject()獲取一個單例的Subject對象,該對象用於綁定本地線程,每個用戶訪問時起一條線程,綁定的就是這個線程,
它是一個自己寫的Thread域,那我們拿到的subject對象就是該域的引用。下面實例化UsernamePasswordToken對象,Subject和UsernamePasswordToken都是由shiro提供的。該token需要傳入用戶提交過來的用戶名和密碼,按上圖參數傳入即可。到這裡就結束了,寫shiro的人還是非常騷氣的.之前的文章裡面我們不是配置了全局異常處理器嗎
s.login(token);方法驗證成功與否需要根據異常判斷,你可以自己trycatch也可以使用springmvc提供的異常處理器來處理。下面我們看看它是怎麼進行用戶名密碼進行校驗的。首先需要在spring的主配置文件中添加如下配置
紅框圈出來的bean是shiro提供的securityManager,他需要注入一個realm。realm是什麼呢,realm就是我們自己編寫的實現用戶名密碼驗證方法的類,他還包含了授權功能。粉色箭頭指向的就是realm的bean,該類由我們自己編寫,稍後見下圖。紅色箭頭是spring提供的通過註解進行代理的類,他需要注入一條屬性,proxyTargetClass是選擇目標代理,如果為true則底層使用cglib代理,如果不配置或者為false,默認為false時,使用JDK代理。JDK除了需要實現接口外,接口中必須封裝子類所有方法,否則會出現異常。因為JDK代理使用的接口中的方法,JAVA8的default關鍵字已經彌補了JDK代理部分缺陷,所有這裡推薦配置為true,使用false也不是不行!最後一個黑色箭頭是shiro提供的切面類,通知由shiro已經編寫完畢,切入點需要我們自行配置。繼續上面添加完realm後我們需要編寫realm中的代碼,realm類必須繼承shiro提供的AuthorizingRealm抽象類,
這個類實現了很多接口,留給我們自己編寫的就兩個方法
doGetAuthorizationInfodoGetAuthenticationInfo
他們兩個長得很像,但是可以看到中間為za的授權方法,為ca的是認證方法。下面我們就先來編寫認證方法
認證方法提供我們一個參數,token就是我們剛才傳入用戶提交的用戶名和密碼的token,需要將其轉型成子類UsernamePasswordToken,然後獲取用戶名。下面要做的就是簡單了,因為我使用的是mybatis,所以直接查資料庫,通過token中的用戶名查詢密碼。select * from user where username = ? 發送一個這樣語句拿到資料庫中的密碼,如果查詢不到對應密碼就會拋出異常AuthenticationException,這裡使用的是shiro1.4版本,如果是1.2或以下版本異常名可能不同。然後我們創建一個SimpleAuthenticationInfo對象,它相當於一個密碼校驗器,需要傳入三個參數,第一個剛才通過username查詢到得實體類對象,查詢到得密碼,如果你的密碼在資料庫中使用加密算法過的話需要將用戶提交進來的密碼也進行加密。第三個參數就是對象名。最後返回SimpleAuthenticationInfo對象,它裡面裝的是幾個boolean類型的值,用來表示密碼驗證是否與用戶提交進來的密碼一致,如果不一致會拋出IncorrectCredentialsException異常。我們通過對異常進行捕獲就能得出是否存在用戶名和密碼是否正確。到此認證方法就結束了,認證通過後將不受到authc過濾器的限制。下面是shiro的授權方法
為了便於演示做了部分改動,perms過濾器是一種權限過濾器,要求訪問該url時必須攜帶後面[]中的權限,它是一個元素為字符串的數組,所以一個url可以配置多種權限。要想使用權限我們需要在資料庫中使用三張表。用戶表、權限表、角色表。我們需要在realm中將這三張表連接起來,可以在資料庫中創建兩個中間表也可以在sql中添加創建臨時表。
下圖為權限表
。下圖角色表
最後是用戶表
他們的關係就是從用戶表開始,用戶表對角色表一對多。角色表對權限表是多對多的關係。下面我們為代碼添加權限.添加權限有四種方式
第一種就是之前圖中的 /editItem = perms["editItem"] 對url訪問權限進行控制,如果沒有該權限則不能訪問url。第二種如圖所示通過註解實現的,
在方法上添加註解,沒有itemList權限的用戶將無法訪問itemList頁面。
第三種為shiro標籤,被標籤包裹住的html代碼將不會顯示
第四種幾乎不會使用所以不需要了解。
添加完權限後我們直接在realm類中完成對用戶授權。
記住授權方法就是中間是za的,我們先實例化SimpleAuthorizationInfo對象,它是用戶權限的容器。然後通過本地線程棧綁定對象SecurityUtils.getSubject().getPrincipal()可以拿到我們剛才從資料庫用過用戶名查詢到得Tuser對象。存到shiro自己寫的ThreadContext域裡去了,現在把它拿出來。接下來通過u.id查詢該用戶擁有的權限。通過
SELECT * FROM auth_function af
LEFT JOIN role_function rf ON rf.function_id = af.id
LEFT JOIN auth_role ar ON ar.id = rf.role_id
LEFT JOIN user_role ur ON rf.role_id = ur.role_id
LEFT JOIN t_user t ON ur.user_id = t.id
WHERE t.id = #{id};
查詢到用戶對應的權限對象集合。然後對集合進行遍歷,沒一個遍歷調用SimpleAuthorizationInfo對象info的add方法將權限對象中的權限添加進去,這裡所謂的權限可以是字母也可以是數字,它是一種標識。添加完成後結束。認證和授權方法都結束以後
這個位置才結束。下一步就是進行跳轉。那麼我們到頁面中看看shiro到底做了什麼
這是沒輸入驗證碼返回該信息,該信息是應該顯示在登錄框內的,我沒寫那個。
密碼不正確時,返回的異常。
用戶名不正確的異常。
沒有登錄權限時返回的信息。以上就是shiro權限和認證的演示了,它支持的功能比較多,還支持緩存用戶權限角色信息等,它的realm也有自己專屬的JDBC,從框架的源碼風格來看spring屬於偏官方的比較通俗易懂的,shiro則是怎麼讓你看不懂怎麼寫。感興趣的小夥伴們可以去看一看我前面的文章.不懂得地方在下面留言回復即可,框架基礎在前面文章都講過了。最後補充一下,本文編譯為spring系列是5.系的版本,shiro系列必須用1.4系版本,其他會造成jar衝突。