Java:Shiro+SpringMVC的整合實踐
原帖位於IT老兵部落格,沉澱著一個IT老兵對於這個行業的認知。
Java:Shiro+SpringMVC的整合實踐。
前言
個人感覺,Shiro的官網有一個問題,講的不夠清楚,儘管看上去好像講的挺明白,但是我總是感覺很多地方不夠清楚,事實上,在閱讀了很多帖子之後,發現很多人都對這一點存在疑問,那就不是我一個人的問題了。
Shiro的官網缺乏完整的例子,而且我所處理的專案是Spring的專案,如何清楚地整合在一起,似乎還沒有看到,很多地方都需要摸索,看了張開濤的部落格,下面一樣有很多人存有疑問。
之前研究這個,花了幾天的時間研究理論,感覺自己已經明白了(這個感覺在另外一篇帖子《Java:Shiro的架構學習筆記》裡面有提到),實際上是,紙上得來終覺淺,絕知此事要躬行。
這篇文章結合著自己的例子,把所理解到的東西做一個總結,以備日後檢視,也給需要的朋友們一個參考。
正文
專案用的是XML配置,至於註解如何配置,暫時還沒有時間去研究。
專案中定義一個spring-shiro.xml檔案,配置在classpath裡面,可以被系統讀取到,這塊涉及Spring讀取配置檔案的功能,官網是寫在了applicationContext.xml檔案裡面,然後在web.xml裡面定義filter,現在做專案,似乎已經很少用到這個web.xml檔案,基本都是定義在spring-mvc.xml這個檔案裡面,這裡給shiro單獨定義了一個配置檔案,原理是一樣的。
先定義filter:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
</bean>
這個將會構造一個shiroFilter,引數是securityManager。
定義securityManager:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="tokenRealm" /> <property name="cacheManager" ref="cacheManager"></property> <property name="sessionManager" ref="sessionManager" /> <property name="subjectFactory" ref="subjectFactory"/> <property name="subjectDAO.sessionStorageEvaluator.sessionStorageEnabled" value="true"/> <!-- By default the servlet container sessions will be used. Uncomment this line to use shiro's native sessions (see the JavaDoc for more): --> <property name="sessionMode" value="http"/> </bean>
這裡構造了securityManager,並且傳遞了6個引數給它,每個引數可以是自己寫的繼承類,也可以是預設的類,這裡涉及一些業務隱私的問題,不能都貼出來了。
第一個tokenRealm是用於進行認證的元件。
<bean id="tokenRealm" class="xx.xx.xx.TokenRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
引數是自定義的一個憑證匹配器。
這裡需要覆寫兩個方法:
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException
返回認證資訊。
和
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
返回授權資訊。
這個地方之前一直沒有搞明白,是最讓我困惑的地方,doGetAuthenticationInfo的第一個引數就是login方法送過來的token,一般這個token帶有username和password,這裡根據這個使用者名稱去把資料庫把使用者的密碼取出來,然後構造一個SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
返回,然後會交由匹配器去匹配,匹配器主要匹配第二個引數(原型是:SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)),即憑證是否相等。
而自定義的匹配器大體是下面這樣,覆寫匹配的函式(增加了快取來儲存嘗試次數):
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username = (String) token.getPrincipal();
AtomicInteger retryCount = loginRetryCache.get(username);
System.out.println("重試次數:" + retryCount);
if (retryCount != null && retryCount.intValue() >= maxRetryCount) {
throw new ExcessiveAttemptsException("username: " + username + " tried to login more than 5 times in period");
}
boolean matches = super.doCredentialsMatch(token, info);
if (matches) {
//clear retry data
System.out.println("清除重試次數快取");
if (retryCount != null) {
loginRetryCache.remove(username);
}
return true;
} else {
if (null == retryCount) {
retryCount = new AtomicInteger(1);
loginRetryCache.put(username, retryCount);
System.out.println("插入快取,失敗次數:" + retryCount);
} else if (retryCount.incrementAndGet() >= maxRetryCount) {
log.warn("username: " + username + " tried to login more than 5 times in period");
throw new ExcessiveAttemptsException("username: " + username + " tried to login more than 5 times in period");
}
retryCount = loginRetryCache.get(username);
System.out.println("認證失敗,失敗次數:" + retryCount);
return false;
}
}
在login完成後,Shiro其實會返回給客戶端一個JSESSIONID,並且會在快取中儲存關於這個會話的一些資訊,這些會話資訊會被定期清理(由排程任務15分鐘或者是下一次訪問時判斷是否過期)或者是由logout方法主動登出掉。
總結
初步總結了一下Shiro的用法,實踐了一天,總結了一天,終於感覺搞明白了,使用Shiro的難度主要在於牽扯的類比較多,而且文件說的不是太清楚,需要自己反覆地實踐。這也可能說明它設計得很靈活,一般設計得很靈活的東西,都是不容易掌握,但是,一旦掌握了,就非常得方便。
這篇帖子還會不斷更新,直到把這個地方的概念全部梳理清楚。
參考
https://shiro.apache.org/10-minute-tutorial.html
https://shiro.apache.org/static/1.3.0/apidocs/org/apache/shiro/authc/SimpleAuthenticationInfo.html
https://shiro.apache.org/architecture.html