项目中的门禁管理者贾维斯———关于 Shiro 框架的 基本使用和基础配置,以及工作流程的生动解释
?這幾天自己做了一個(gè)小的demo,學(xué)習(xí)并使用了shiro框架用來管理我的登錄與授權(quán).學(xué)習(xí)這個(gè)框架到成功運(yùn)用用了三天時(shí)間,前兩天都是出于理解和踩坑階段,第三天才真正開始擼起了代碼,并自測(cè)成功.
首先我是通過這個(gè)這個(gè)網(wǎng)站學(xué)習(xí)的shiro
How2J 的 Java教程? ? ? http://how2j.cn/?
這個(gè)網(wǎng)站非常好,涉及的知識(shí)面也特別的廣,大家可以來收藏.
有了初步的了解后,我來捋一下我的shiro 的理解.
shiro是用來做登錄管理和授權(quán)角色管理的,也就是說關(guān)于"登錄"這部分都可以交給shiro去做.
打個(gè)比方: 只能登陸后才能訪問的部分,可以比作是我們的家.登錄頁面,就是門禁系統(tǒng),那shiro,就是這個(gè)門禁系統(tǒng)的核心,更形象一點(diǎn)說,這個(gè)shiro就是我們的管家(類似于鋼鐵俠的賈維斯).
我們可以通過配置賈維斯來控制哪些人(也就是代碼里的鏈接)能進(jìn)家里而不需要身份認(rèn)證,哪些人必須要認(rèn)證后才能進(jìn)來.一旦賈維斯識(shí)別出這個(gè)人不是我們提前設(shè)置好的,那就統(tǒng)統(tǒng)給我滾回到門禁那里去做身份識(shí)別(也就是登錄頁面).這部分是shiro的登錄認(rèn)證管理.
通過了身份驗(yàn)證后,你就可以通過門禁系統(tǒng)而進(jìn)入到我家里(也就是項(xiàng)目中的其他部分),并且我們的賈維斯能夠記住你的身份,你的身份信息都被保存下來了.我們也可以隨時(shí)隨地都可以拿到你的身份信息.
進(jìn)入我家后也不是所有的房間你都能進(jìn)去,比如我的書房只有我的最親的人才能進(jìn)去.那當(dāng)你想要進(jìn)入我的書房時(shí),賈維斯就開始識(shí)別你的身份,來看看你是不是我最親的人.如果發(fā)現(xiàn)你不是,那你就沒有權(quán)限進(jìn)入書房,如果是,那就能順利進(jìn)入.這部分就是shiro的身份授權(quán)管理.
到這里,大家都對(duì)shiro是干嘛的有了一個(gè)基本的了解了.簡(jiǎn)單點(diǎn)說,shiro就是個(gè)攔截器,就像哨卡一樣,攔住你,盤問你,識(shí)別你.不過有一點(diǎn)要注意,shiro只是來輔助你做這些事,至于哪些鏈接需要認(rèn)證,哪些不需要,權(quán)限與角色的配置,這些都算是業(yè)務(wù)邏輯,必須由我們?nèi)ネ瓿刹趴梢?
接下來,讓我們來看看,要構(gòu)建這么一個(gè)牛逼的安全管家應(yīng)該怎么做.
由于現(xiàn)在用的比較多是SSM框架,所以這次我講述的也都是在SSM框架環(huán)境下的shiro,其他環(huán)境下的我就不在這里贅述了.
首先導(dǎo)包.
就是ssm框架的那一套都導(dǎo)進(jìn)去,然后 去shiro的官方http://shiro.apache.org/download.html上去下載 shiro的包,如果是maven項(xiàng)目,官網(wǎng)里也有pom中的各種依賴,復(fù)制粘貼就OK.
接下來搞SSM和shiro的配置文件.
配置文件有:
web.xml? ? ? 管理配置文件,sprigmvc核心分發(fā)servlet以及shiro 過濾器的配置
spring-mvc.xml? ? ? ? springmvc的核心配置文件
spring-shiro.xml? ? ? shiro的核心配置文件,其實(shí)也可以與sping-mvc.xml寫在一起
spring-mybatis.xml? ? ?管理數(shù)據(jù)庫連接的
各配置的文件名不重要,重要的是里面的內(nèi)容,并且我在這里列出要注意的和新增加的xml代碼,常規(guī)的框架配置我就不再贅述.
一.首先是web.xml
因?yàn)槲疫@里把shiro單獨(dú)拿出來做一個(gè)xml配置文件了,所以在做xml引用時(shí),我們要把這個(gè)文件加進(jìn)去,由于我命名都是按照"spring-"為前綴,所以用了一個(gè)通配符,如果你的文件名不是這樣的,那就分別寫出來,就像注釋里的那樣.
<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-*.xml<!--classpath:applicationContext.xml,classpath:applicationContext-shiro.xml--></param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>然后配置shiro過濾器
<!-- shiro過濾器定義 --><filter><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class><init-param><!-- 該值缺省為false,表示生命周期由SpringApplicationContext管理,設(shè)置為true則表示由ServletContainer管理 --><param-name>targetFilterLifecycle</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>二.spring-mvc.xml
1. springmvc的基本配置(這部分代碼不展示了)
? ? ? ? ? ?2. 增加了對(duì)shiro的支持? ? 這樣可以在控制器Controller上,使用像@RequireRole 這樣的注解,來表示某個(gè)方法必須有相關(guān)的角色才能訪問
? ? ? ? ? ?3. 指定了異常處理類DefaultExceptionHandler,這樣當(dāng)訪問沒有權(quán)限的資源的時(shí)候,就會(huì)跳到統(tǒng)一的頁面去顯示錯(cuò)誤信息,這條可有可無,看你的邏輯,如果需要可以配置
三.spring-shiro.xml
這部分很關(guān)鍵,我把所有的代碼都貼上來
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util"xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"><context:component-scan base-package="com.blog.service"/><!-- 配置shiro的過濾器工廠類,id- shiroFilter要和我們?cè)趙eb.xml中配置的過濾器一致 --><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><!-- 調(diào)用我們配置的權(quán)限管理器 --><property name="securityManager" ref="securityManager" /><!-- 配置我們的登錄請(qǐng)求地址 --><property name="loginUrl" value="/login" /><!-- 如果您請(qǐng)求的資源不再您的權(quán)限范圍,則跳轉(zhuǎn)到/403請(qǐng)求地址 --><property name="unauthorizedUrl" value="/unauthorized" /><!-- 退出 --><property name="filters"><util:map><entry key="logout" value-ref="logoutFilter" /></util:map></property><!-- 權(quán)限配置 --><property name="filterChainDefinitions"><value><!-- anon表示此地址不需要任何權(quán)限即可訪問 -->/loginBlogger=anon/index=anon/static/**=anon/doLogout=logout<!--所有的請(qǐng)求(除去配置的靜態(tài)資源請(qǐng)求或請(qǐng)求地址為anon的請(qǐng)求)都要通過登錄驗(yàn)證,如果未登錄則跳到/login -->/** = authc</value></property></bean><!-- 退出過濾器 --><bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter"><property name="redirectUrl" value="/login" /></bean><!-- 會(huì)話ID生成器 --><bean id="sessionIdGenerator"class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" /><!-- 會(huì)話Cookie模板 關(guān)閉瀏覽器立即失效 --><bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"><constructor-arg value="sid" /><property name="httpOnly" value="true" /><property name="maxAge" value="-1" /></bean><!-- 會(huì)話DAO --><bean id="sessionDAO"class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"><property name="sessionIdGenerator" ref="sessionIdGenerator" /></bean><!-- 會(huì)話驗(yàn)證調(diào)度器,每30分鐘執(zhí)行一次驗(yàn)證 ,設(shè)定會(huì)話超時(shí)及保存 --><bean name="sessionValidationScheduler"class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler"><property name="interval" value="1800000" /><property name="sessionManager" ref="sessionManager" /></bean><!-- 會(huì)話管理器 --><bean id="sessionManager"class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"><!-- 全局會(huì)話超時(shí)時(shí)間(單位毫秒),默認(rèn)30分鐘 --><property name="globalSessionTimeout" value="1800000" /><property name="deleteInvalidSessions" value="true" /><property name="sessionValidationSchedulerEnabled" value="true" /><property name="sessionValidationScheduler" ref="sessionValidationScheduler" /><property name="sessionDAO" ref="sessionDAO" /><property name="sessionIdCookieEnabled" value="true" /><property name="sessionIdCookie" ref="sessionIdCookie" /></bean><!-- 安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="databaseRealm" /><property name="sessionManager" ref="sessionManager" /></bean><!-- 相當(dāng)于調(diào)用SecurityUtils.setSecurityManager(securityManager) --><beanclass="org.springframework.beans.factory.config.MethodInvokingFactoryBean"><property name="staticMethod"value="org.apache.shiro.SecurityUtils.setSecurityManager" /><property name="arguments" ref="securityManager" /></bean><bean id="databaseRealm" class="com.blog.realm.DatabaseRealm"></bean><!-- 保證實(shí)現(xiàn)了Shiro內(nèi)部lifecycle函數(shù)的bean執(zhí)行 --><bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> </beans>具體哪個(gè)是做什么的,我不多說.我會(huì)著重說幾個(gè).
<context:component-scan base-package="com.blog.service"/>你會(huì)疑問了,這個(gè)為啥子在這里配呢.這個(gè)不應(yīng)該springmvc.xml的配置文件里配嗎? 沒錯(cuò),常規(guī)來說,確實(shí)是這樣的.但是如果不在shiro.xml里配,到時(shí)候再shiro的realm中使用service層就會(huì)報(bào)錯(cuò).可以看看這篇博客.
shiro框架,自定義realm注入service失敗解決辦法 - u33445687的博客 - CSDN博客
https://blog.csdn.net/u33445687/article/details/83617510
這部分很重要,這部分就是告訴你的賈維斯,哪些人能過,哪些人是需要認(rèn)證才能過的.以及退出指令等等.
<bean id="databaseRealm" class="com.blog.realm.DatabaseRealm"></bean>這個(gè)是告訴shiro我們的realm是哪個(gè).realm是什么,這個(gè)我們一會(huì)再說,你就先配置上就行.
至于其他的代碼,都不用動(dòng),直接復(fù)制粘貼就行,當(dāng)然了,那些配置具體是做什么的,可以自行去查找,網(wǎng)上都有.
? ? 四.spring-mybatis.xml? ? 這個(gè)就是Mybatis的那一套, 沒啥說的
我們看看登錄效果哈
首先去登錄,登陸失敗,會(huì)到登錄頁面,并展示出錯(cuò)誤信息.登錄成功后,這時(shí)復(fù)制主頁鏈接,點(diǎn)擊安全退出后,在地址欄輸入主頁鏈接,會(huì)自動(dòng)跳回登錄頁面.
接下來看看實(shí)現(xiàn)的代碼
首先要有個(gè)登錄頁,這不必說了,表單的地址要為可通過的(即要在shiro.xml配置好)
后臺(tái)登錄的controller代碼如下:
@Controller @RequestMapping("") public class LoginController {@RequestMapping("/loginBlogger")public String login(Model model,Blogger blogger) {//獲取當(dāng)前用戶的shiro信息Subject currentUser = SecurityUtils.getSubject(); if (currentUser.isAuthenticated()) {return "redirect:/backgroundIndex";}//token安全令牌UsernamePasswordToken token = new UsernamePasswordToken(blogger.getUserName(), blogger.getPassword()); try { //調(diào)用登錄,會(huì)自動(dòng)進(jìn)入realmcurrentUser.login(token);Session session=currentUser.getSession();session.setAttribute("subject", currentUser);//登陸成功return "redirect:/backgroundIndex";//登陸失敗.拋出異常} catch (AuthenticationException e) { model.addAttribute("error", "驗(yàn)證失敗"); return "/background/login.jsp"; } } }這個(gè)LoginController就相當(dāng)于門禁系統(tǒng),他會(huì)去先判斷有沒有賬號(hào)登錄了,如果有登錄,就會(huì)直接跳到主頁去,如果沒有,他就會(huì)拿著前臺(tái)傳過來的賬號(hào)密碼,來封裝成一個(gè)安全令牌token,也可以理解為一個(gè)小紙條,上面有賬號(hào)密碼,然后拿著這個(gè)token去找我們的賈維斯,也就是realm,賈維斯開始判斷,你這個(gè)過來的小紙條上的人可不可以通過.currentUser.login(token)執(zhí)行時(shí)就會(huì)去realm,通過斷點(diǎn)debug可以發(fā)現(xiàn),他就會(huì)自動(dòng)進(jìn)入realm方法.
現(xiàn)在開始說這個(gè)shiro中最最關(guān)鍵的realm,英文翻譯為"域",在我們這里就是可以理解為賈維斯.賈維斯會(huì)進(jìn)行身份驗(yàn)證,會(huì)進(jìn)行角色認(rèn)證,也會(huì)進(jìn)行授權(quán)認(rèn)證(今天我們的例子中只有登錄身份驗(yàn)證,等下期再說角色授權(quán)).
來看看realm的代碼:著重去看登錄與驗(yàn)證這部分
public class DatabaseRealm extends AuthorizingRealm {@Autowiredprivate ILoginService loginSerivce;/*** 角色與權(quán)限*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}/*** 登錄與驗(yàn)證*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//獲取賬號(hào)密碼UsernamePasswordToken t = (UsernamePasswordToken) token;String userName= token.getPrincipal().toString();String password= new String( t.getPassword());//獲取數(shù)據(jù)庫中的密碼Blogger blogger = loginSerivce.getBloggerByName(userName);//如果為空就是賬號(hào)不存在,如果不相同就是密碼錯(cuò)誤,但是都拋出AuthenticationException,而不是拋出具體錯(cuò)誤原因,免得給破解者提供幫助信息if(null==blogger.getPassword() || !blogger.getPassword().equals(password)) throw new AuthenticationException();//認(rèn)證信息里存放賬號(hào)密碼, getName() 是當(dāng)前Realm的繼承方法,通常返回當(dāng)前類名 :databaseRealmSimpleAuthenticationInfo a = new SimpleAuthenticationInfo(blogger,blogger.getPassword(),getName());return a;}}邏輯很簡(jiǎn)單,就是獲取到從前臺(tái)傳過來的token,也即是小紙條.然后得到紙條上的賬號(hào)和密碼,一般情況下,用戶名都是唯一存在的,所以我們可以用用戶名去數(shù)據(jù)庫查找獲得用戶,獲得后,再去進(jìn)行判斷.如果賬號(hào)或密碼不對(duì),就要拋出異常.如果賬號(hào)和密碼匹對(duì)上了,我們就要把登陸的用戶存放在SimpleAuthenticationInfo認(rèn)證信息當(dāng)中,意思就是告訴讓賈維斯知道,這個(gè)人登陸成功了,也是為了方便我們?cè)谝院蟮臉I(yè)務(wù)邏輯當(dāng)中隨時(shí)能夠獲取當(dāng)當(dāng)前登錄的人.
注意,SimpleAuthenticationInfo 這個(gè)對(duì)象中的第一個(gè)參數(shù),我們盡量要存進(jìn)去的是這個(gè)登錄人的實(shí)例對(duì)象,也是方便以后獲取,網(wǎng)上很多案例存的都是用戶名.但是萬一以后我們要修改賬戶信息的話,我還得從數(shù)據(jù)庫查一遍,不如現(xiàn)在就存?zhèn)€實(shí)例對(duì)象,直接調(diào)用即可.
你也許會(huì)問,為啥在LoginController中執(zhí)行currentUser.login(token)時(shí)就會(huì)走到這個(gè)realm中呢.?還記得我們?cè)谂渲胹hiro.xml時(shí)配置過我們的realm路徑不?
<bean id="databaseRealm" class="com.blog.realm.DatabaseRealm"></bean>class中要寫我們r(jià)ealm的全路徑名,這樣就能找到了.
至于退出的話,無非就是向后臺(tái)發(fā)送一個(gè)關(guān)于退出的請(qǐng)求,就是在shiro.xml中配置的那個(gè),發(fā)送后就會(huì)退出,并跳到相應(yīng)的路徑.
到這本次博客就基本結(jié)束了.
由于我的資歷尚淺,道行不深,大部分也都是在網(wǎng)上找的,然后再加上自己的理解總結(jié)出來的.基本上也就是會(huì)用,至于為什么這么用,我自己還有很多很多不理解的地方.不管怎樣,先跑起來,然后再一點(diǎn)一點(diǎn)地消化
目前我自己的這個(gè)小項(xiàng)目還沒有涉及到角色權(quán)限這塊,等我研究研究的再來寫一篇關(guān)于權(quán)限與角色.
總結(jié)
以上是生活随笔為你收集整理的项目中的门禁管理者贾维斯———关于 Shiro 框架的 基本使用和基础配置,以及工作流程的生动解释的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。