這幾天自己做了一個小的demo,學習並使用了shiro框架用來管理我的登錄與授權.學習這個框架到成功運用用了三天時間,前兩天都是出於理解和踩坑階段,第三天才真正開始擼起了代碼,並自測成功.
首先我是通過這個這個網站學習的shiro
How2J 的 Java教程 http://how2j.cn/
這個網站非常好,涉及的知識面也特別的廣,大家可以來收藏.
有了初步的瞭解後,我來捋一下我的shiro 的理解.
shiro是用來做登錄管理和授權角色管理的,也就是說關於"登錄"這部分都可以交給shiro去做.
打個比方: 只能登陸後才能訪問的部分,可以比作是我們的家.登錄頁面,就是門禁系統,那shiro,就是這個門禁系統的核心,更形象一點說,這個shiro就是我們的管家(類似於鋼鐵俠的賈維斯).
我們可以通過配置賈維斯來控制哪些人(也就是代碼裏的鏈接)能進家裏而不需要身份認證,哪些人必須要認證後才能進來.一旦賈維斯識別出這個人不是我們提前設置好的,那就統統給我滾回到門禁那裏去做身份識別(也就是登錄頁面).這部分是shiro的登錄認證管理.
通過了身份驗證後,你就可以通過門禁系統而進入到我家裏(也就是項目中的其他部分),並且我們的賈維斯能夠記住你的身份,你的身份信息都被保存下來了.我們也可以隨時隨地都可以拿到你的身份信息.
進入我家後也不是所有的房間你都能進去,比如我的書房只有我的最親的人才能進去.那當你想要進入我的書房時,賈維斯就開始識別你的身份,來看看你是不是我最親的人.如果發現你不是,那你就沒有權限進入書房,如果是,那就能順利進入.這部分就是shiro的身份授權管理.
到這裏,大家都對shiro是幹嘛的有了一個基本的瞭解了.簡單點說,shiro就是個攔截器,就像哨卡一樣,攔住你,盤問你,識別你.不過有一點要注意,shiro只是來輔助你做這些事,至於哪些鏈接需要認證,哪些不需要,權限與角色的配置,這些都算是業務邏輯,必須由我們去完成纔可以.
接下來,讓我們來看看,要構建這麼一個牛逼的安全管家應該怎麼做.
由於現在用的比較多是SSM框架,所以這次我講述的也都是在SSM框架環境下的shiro,其他環境下的我就不在這裏贅述了.
首先導包.
就是ssm框架的那一套都導進去,然後 去shiro的官方http://shiro.apache.org/download.html上去下載 shiro的包,如果是maven項目,官網裏也有pom中的各種依賴,複製粘貼就OK.
接下來搞SSM和shiro的配置文件.
配置文件有:
web.xml 管理配置文件,sprigmvc核心分發servlet以及shiro 過濾器的配置
spring-mvc.xml springmvc的核心配置文件
spring-shiro.xml shiro的核心配置文件,其實也可以與sping-mvc.xml寫在一起
spring-mybatis.xml 管理數據庫連接的
各配置的文件名不重要,重要的是裏面的內容,並且我在這裏列出要注意的和新增加的xml代碼,常規的框架配置我就不再贅述.
一.首先是web.xml
因爲我這裏把shiro單獨拿出來做一個xml配置文件了,所以在做xml引用時,我們要把這個文件加進去,由於我命名都是按照"spring-"爲前綴,所以用了一個通配符,如果你的文件名不是這樣的,那就分別寫出來,就像註釋裏的那樣.
<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管理,設置爲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. 增加了對shiro的支持 這樣可以在控制器Controller上,使用像@RequireRole 這樣的註解,來表示某個方法必須有相關的角色才能訪問
3. 指定了異常處理類DefaultExceptionHandler,這樣當訪問沒有權限的資源的時候,就會跳到統一的頁面去顯示錯誤信息,這條可有可無,看你的邏輯,如果需要可以配置
<!--啓用shiro註解 -->
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<!-- 控制器異常處理 -->
<bean id="exceptionHandlerExceptionResolver" class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
</bean>
<bean class="com.how2java.exception.DefaultExceptionHandler"/>
三.spring-shiro.xml
這部分很關鍵,我把所有的代碼都貼上來
<?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/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop
http://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要和我們在web.xml中配置的過濾器一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 調用我們配置的權限管理器 -->
<property name="securityManager" ref="securityManager" />
<!-- 配置我們的登錄請求地址 -->
<property name="loginUrl" value="/login" />
<!-- 如果您請求的資源不再您的權限範圍,則跳轉到/403請求地址 -->
<property name="unauthorizedUrl" value="/unauthorized" />
<!-- 退出 -->
<property name="filters">
<util:map>
<entry key="logout" value-ref="logoutFilter" />
</util:map>
</property>
<!-- 權限配置 -->
<property name="filterChainDefinitions">
<value>
<!-- anon表示此地址不需要任何權限即可訪問 -->
/loginBlogger=anon
/index=anon
/static/**=anon
/doLogout=logout
<!--所有的請求(除去配置的靜態資源請求或請求地址爲anon的請求)都要通過登錄驗證,如果未登錄則跳到/login -->
/** = authc
</value>
</property>
</bean>
<!-- 退出過濾器 -->
<bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
<property name="redirectUrl" value="/login" />
</bean>
<!-- 會話ID生成器 -->
<bean id="sessionIdGenerator"
class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" />
<!-- 會話Cookie模板 關閉瀏覽器立即失效 -->
<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>
<!-- 會話DAO -->
<bean id="sessionDAO"
class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="sessionIdGenerator" ref="sessionIdGenerator" />
</bean>
<!-- 會話驗證調度器,每30分鐘執行一次驗證 ,設定會話超時及保存 -->
<bean name="sessionValidationScheduler"
class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
<property name="interval" value="1800000" />
<property name="sessionManager" ref="sessionManager" />
</bean>
<!-- 會話管理器 -->
<bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- 全局會話超時時間(單位毫秒),默認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>
<!-- 相當於調用SecurityUtils.setSecurityManager(securityManager) -->
<bean
class="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>
<!-- 保證實現了Shiro內部lifecycle函數的bean執行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
</beans>
具體哪個是做什麼的,我不多說.我會着重說幾個.
<context:component-scan base-package="com.blog.service"/>
你會疑問了,這個爲啥子在這裏配呢.這個不應該springmvc.xml的配置文件裏配嗎? 沒錯,常規來說,確實是這樣的.但是如果不在shiro.xml裏配,到時候再shiro的realm中使用service層就會報錯.可以看看這篇博客.
shiro框架,自定義realm注入service失敗解決辦法 - u33445687的博客 - CSDN博客
https://blog.csdn.net/u33445687/article/details/83617510
<!-- 配置shiro的過濾器工廠類,id- shiroFilter要和我們在web.xml中配置的過濾器一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 調用我們配置的權限管理器 -->
<property name="securityManager" ref="securityManager" />
<!-- 配置我們的登錄請求地址 -->
<property name="loginUrl" value="/login" />
<!-- 如果您請求的資源不再您的權限範圍,則跳轉到/403請求地址 -->
<property name="unauthorizedUrl" value="/unauthorized" />
<!-- 退出 -->
<property name="filters">
<util:map>
<entry key="logout" value-ref="logoutFilter" />
</util:map>
</property>
<!-- 權限配置 -->
<property name="filterChainDefinitions">
<value>
<!-- anon表示此地址不需要任何權限即可訪問 -->
/loginBlogger=anon
/index=anon
/static/**=anon
/doLogout=logout
<!--所有的請求(除去配置的靜態資源請求或請求地址爲anon的請求)都要通過登錄驗證,如果未登錄則跳到/login -->
/** = authc
</value>
</property>
</bean>
這部分很重要,這部分就是告訴你的賈維斯,哪些人能過,哪些人是需要認證才能過的.以及退出指令等等.
<bean id="databaseRealm" class="com.blog.realm.DatabaseRealm">
</bean>
這個是告訴shiro我們的realm是哪個.realm是什麼,這個我們一會再說,你就先配置上就行.
至於其他的代碼,都不用動,直接複製粘貼就行,當然了,那些配置具體是做什麼的,可以自行去查找,網上都有.
四.spring-mybatis.xml 這個就是Mybatis的那一套, 沒啥說的
我們看看登錄效果哈
首先去登錄,登陸失敗,會到登錄頁面,並展示出錯誤信息.登錄成功後,這時複製主頁鏈接,點擊安全退出後,在地址欄輸入主頁鏈接,會自動跳回登錄頁面.
接下來看看實現的代碼
首先要有個登錄頁,這不必說了,表單的地址要爲可通過的(即要在shiro.xml配置好)
後臺登錄的controller代碼如下:
@Controller
@RequestMapping("")
public class LoginController {
@RequestMapping("/loginBlogger")
public String login(Model model,Blogger blogger) {
//獲取當前用戶的shiro信息
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.isAuthenticated()) {
return "redirect:/backgroundIndex";
}
//token安全令牌
UsernamePasswordToken token = new UsernamePasswordToken(blogger.getUserName(), blogger.getPassword());
try {
//調用登錄,會自動進入realm
currentUser.login(token);
Session session=currentUser.getSession();
session.setAttribute("subject", currentUser);
//登陸成功
return "redirect:/backgroundIndex";
//登陸失敗.拋出異常
} catch (AuthenticationException e) {
model.addAttribute("error", "驗證失敗");
return "/background/login.jsp";
}
}
}
這個LoginController就相當於門禁系統,他會去先判斷有沒有賬號登錄了,如果有登錄,就會直接跳到主頁去,如果沒有,他就會拿着前臺傳過來的賬號密碼,來封裝成一個安全令牌token,也可以理解爲一個小紙條,上面有賬號密碼,然後拿着這個token去找我們的賈維斯,也就是realm,賈維斯開始判斷,你這個過來的小紙條上的人可不可以通過.currentUser.login(token)執行時就會去realm,通過斷點debug可以發現,他就會自動進入realm方法.
現在開始說這個shiro中最最關鍵的realm,英文翻譯爲"域",在我們這裏就是可以理解爲賈維斯.賈維斯會進行身份驗證,會進行角色認證,也會進行授權認證(今天我們的例子中只有登錄身份驗證,等下期再說角色授權).
來看看realm的代碼:着重去看登錄與驗證這部分
public class DatabaseRealm extends AuthorizingRealm {
@Autowired
private ILoginService loginSerivce;
/**
* 角色與權限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 登錄與驗證
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//獲取賬號密碼
UsernamePasswordToken t = (UsernamePasswordToken) token;
String userName= token.getPrincipal().toString();
String password= new String( t.getPassword());
//獲取數據庫中的密碼
Blogger blogger = loginSerivce.getBloggerByName(userName);
//如果爲空就是賬號不存在,如果不相同就是密碼錯誤,但是都拋出AuthenticationException,而不是拋出具體錯誤原因,免得給破解者提供幫助信息
if(null==blogger.getPassword() || !blogger.getPassword().equals(password))
throw new AuthenticationException();
//認證信息裏存放賬號密碼, getName() 是當前Realm的繼承方法,通常返回當前類名 :databaseRealm
SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(blogger,blogger.getPassword(),getName());
return a;
}
}
邏輯很簡單,就是獲取到從前臺傳過來的token,也即是小紙條.然後得到紙條上的賬號和密碼,一般情況下,用戶名都是唯一存在的,所以我們可以用用戶名去數據庫查找獲得用戶,獲得後,再去進行判斷.如果賬號或密碼不對,就要拋出異常.如果賬號和密碼匹對上了,我們就要把登陸的用戶存放在SimpleAuthenticationInfo認證信息當中,意思就是告訴讓賈維斯知道,這個人登陸成功了,也是爲了方便我們在以後的業務邏輯當中隨時能夠獲取噹噹前登錄的人.
注意,SimpleAuthenticationInfo 這個對象中的第一個參數,我們儘量要存進去的是這個登錄人的實例對象,也是方便以後獲取,網上很多案例存的都是用戶名.但是萬一以後我們要修改賬戶信息的話,我還得從數據庫查一遍,不如現在就存個實例對象,直接調用即可.
你也許會問,爲啥在LoginController中執行currentUser.login(token)時就會走到這個realm中呢.?還記得我們在配置shiro.xml時配置過我們的realm路徑不?
<bean id="databaseRealm" class="com.blog.realm.DatabaseRealm">
</bean>
class中要寫我們realm的全路徑名,這樣就能找到了.
至於退出的話,無非就是向後臺發送一個關於退出的請求,就是在shiro.xml中配置的那個,發送後就會退出,並跳到相應的路徑.
到這本次博客就基本結束了.
由於我的資歷尚淺,道行不深,大部分也都是在網上找的,然後再加上自己的理解總結出來的.基本上也就是會用,至於爲什麼這麼用,我自己還有很多很多不理解的地方.不管怎樣,先跑起來,然後再一點一點地消化
目前我自己的這個小項目還沒有涉及到角色權限這塊,等我研究研究的再來寫一篇關於權限與角色.