前言
本文主要講解的知識點有以下:
- Shiro授權過濾器使用
- Shiro快取
- 與Ehcache整合
- Shiro應用->實現驗證碼功能
- 記住我功能
一、授權過濾器測試
我們的授權過濾器使用的是permissionsAuthorizationFilter來進行攔截。我們可以在application-shiro中配置filter規則
<!--商品查詢需要商品查詢許可權 -->
/items/queryItems.action = perms[item:query]
/items/editItems.action = perms[item:edit]
複製程式碼
測試流程: 1、在applicationContext-shiro.xml中配置filter規則
<!--商品查詢需要商品查詢許可權 -->
- /items/queryItems.action = perms[item:query]
2、使用者在認證通過後,請求/items/queryItems.action 3、被PermissionsAuthorizationFilter攔截,發現需要“item:query”許可權 4、PermissionsAuthorizationFilter 呼叫realm中的doGetAuthorizationInfo獲取資料庫中正確的許可權 5、PermissionsAuthorizationFilter對item:query 和從realm中獲取許可權進行對比,如果“item:query”在realm返回的許可權列表中,授權通過。
realm中獲取認證的資訊,查詢出該使用者對應的許可權,封裝到simpleAuthorizationInfo中,PermissionsAuthorizationFilter會根據對應的許可權來比對。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
//從 principals獲取主身份資訊
//將getPrimaryPrincipal方法返回值轉為真實身份型別(在上邊的doGetAuthenticationInfo認證通過填充到SimpleAuthenticationInfo中身份型別),
ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
//根據身份資訊獲取許可權資訊
//從資料庫獲取到許可權資料
List<SysPermission> permissionList = null;
try {
permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//單獨定一個集合物件
List<String> permissions = new ArrayList<String>();
if(permissionList!=null){
for(SysPermission sysPermission:permissionList){
//將資料庫中的許可權標籤 符放入集合
permissions.add(sysPermission.getPercode());
}
}
/* List<String> permissions = new ArrayList<String>();
permissions.add("user:create");//使用者的建立
permissions.add("item:query");//商品查詢許可權
permissions.add("item:add");//商品新增許可權
permissions.add("item:edit");//商品修改許可權
*/ //....
//查到許可權資料,返回授權資訊(要包括 上邊的permissions)
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//將上邊查詢到授權資訊填充到simpleAuthorizationInfo物件中
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
}
複製程式碼
在bean中我們已經配置了:如果沒有許可權,那麼跳轉到哪個JSP頁面了
<!-- 通過unauthorizedUrl指定沒有許可權操作時跳轉頁面-->
<property name="unauthorizedUrl" value="/refuse.jsp" />
複製程式碼
到目前為止,現在問題又來了:
1、在applicationContext-shiro.xml中配置過慮器連結,需要將全部的url和許可權對應起來進行配置,比較發麻不方便使用。
2、每次授權都需要呼叫realm查詢資料庫,對於系統效能有很大影響,可以通過shiro快取來解決。
二、使用註解式和標籤式配置授權
上面的那種方法,還是需要我們將全部的url和許可權對應起來進行配置,是比較不方便的。我們可以使用授權的另外兩種方式
- 註解式
- 標籤式
2.1註解式
如果要使用註解式,那麼就必須在Spring中開啟controller類aop支援
<!-- 開啟aop,對類代理 -->
<aop:config proxy-target-class="true"></aop:config>
<!-- 開啟shiro註解支援 -->
<bean
class="
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
複製程式碼
在Controller中使用註解來進行配置就行了,就不用在我們的application-shiro中全部集中配置了
//商品資訊方法
@RequestMapping("/queryItems")
@RequiresPermissions("item:query")//執行queryItems需要"item:query"許可權
public ModelAndView queryItems(HttpServletRequest request) throws Exception {
System.out.println(request.getParameter("id"));
//呼叫service查詢商品列表
List<ItemsCustom> itemsList = itemsService.findItemsList(null);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("itemsList", itemsList);
// 指定邏輯檢視名
modelAndView.setViewName("itemsList");
return modelAndView;
}
複製程式碼
2.2jsp標籤 授權
當呼叫controller的一個方法,由於該 方法加了@RequiresPermissions("item:query") ,shiro呼叫realm獲取資料庫中的許可權資訊,看"item:query"是否在許可權資料中存在,如果不存在就拒絕訪問,如果存在就授權通過。
當展示一個jsp頁面時,頁面中如果遇到<shiro:hasPermission name="item:update">,shiro呼叫realm獲取資料庫中的許可權資訊,看item:update是否在許可權資料中存在,如果不存在就拒絕訪問,如果存在就授權通過。
三、Shiro快取
針對上邊授權頻繁查詢資料庫,需要使用shiro快取
3.1快取流程
shiro中提供了對認證資訊和授權資訊的快取。shiro預設是關閉認證資訊快取的,對於授權資訊的快取shiro預設開啟的。主要研究授權資訊快取,因為授權的資料量大。
使用者認證通過。
該使用者第一次授權:呼叫realm查詢資料庫 該使用者第二次授權:不呼叫realm查詢資料庫,直接從快取中取出授權資訊(許可權識別符號)。
3.2使用ehcache和Shiro整合
匯入jar包
配置快取管理器,注入到安全管理器中
<!-- 快取管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>
複製程式碼
<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm" />
<!-- 注入快取管理器 -->
<property name="cacheManager" ref="cacheManager"/>
</bean>
複製程式碼
ehcache的配置檔案shiro-ehcache.xml:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--diskStore:快取資料持久化的目錄 地址 -->
<diskStore path="F:\develop\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
複製程式碼
3.3快取清空
如果使用者正常退出,快取自動清空。 如果使用者非正常退出,快取自動清空。
還有一種情況:
- 當管理員修改了使用者的許可權,但是該使用者還沒有退出,在預設情況下**,修改的許可權無法立即生效**。需要手動進行程式設計實現:在許可權修改後呼叫realm的clearCache方法清除快取。
清除快取:
//清除快取
public void clearCached() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
複製程式碼
3.4sessionManager
和shiro整合後,使用shiro的session管理,shiro提供sessionDao操作 會話資料。
配置sessionManager
<!-- 會話管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效時長,單位毫秒 -->
<property name="globalSessionTimeout" value="600000"/>
<!-- 刪除失效的session -->
<property name="deleteInvalidSessions" value="true"/>
</bean>
複製程式碼
注入到安全管理器中
<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm" />
<!-- 注入快取管理器 -->
<property name="cacheManager" ref="cacheManager"/>
<!-- 注入session管理器 -->
<property name="sessionManager" ref="sessionManager" />
</bean>
複製程式碼
四、驗證碼
在登陸的時候,我們一般都設定有驗證碼,但是我們如果使用Shiro的話,那麼Shiro預設的是使用FormAuthenticationFilter進行表單認證。
而我們的驗證校驗的功能應該加在FormAuthenticationFilter中,在認證之前進行驗證碼校驗。
FormAuthenticationFilter是Shiro預設的功能,我們想要在FormAuthenticationFilter之前進行驗證碼校驗,就需要繼承FormAuthenticationFilter類,改寫它的認證方法!
4.1自定義Form認證類
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
//原FormAuthenticationFilter的認證方法
@Override
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception {
//在這裡進行驗證碼的校驗
//從session獲取正確驗證碼
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpSession session =httpServletRequest.getSession();
//取出session的驗證碼(正確的驗證碼)
String validateCode = (String) session.getAttribute("validateCode");
//取出頁面的驗證碼
//輸入的驗證和session中的驗證進行對比
String randomcode = httpServletRequest.getParameter("randomcode");
if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
//如果校驗失敗,將驗證碼錯誤失敗資訊,通過shiroLoginFailure設定到request中
httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
//拒絕訪問,不再校驗賬號和密碼
return true;
}
return super.onAccessDenied(request, response);
}
}
複製程式碼
4.2配置自定義類
我們編寫完自定義類以後,是需要在Shiro配置檔案中配置我們這個自定義類的。
由於這是我們自定義的,因此我們並不需要使用者名稱就使用username,密碼就使用password,這個也是我們可以自定義的。
<!-- 自定義form認證過慮器 -->
<!-- 基於Form表單的身份驗證過濾器,不配置將也會註冊此過慮器,表單中的使用者賬號、密碼及loginurl將採用預設值,建議配置 -->
<bean id="formAuthenticationFilter"
class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter ">
<!-- 表單中賬號的input名稱 -->
<property name="usernameParam" value="username" />
<!-- 表單中密碼的input名稱 -->
<property name="passwordParam" value="password" />
</bean>
複製程式碼
在Shiro的bean中注入自定義的過濾器
<!-- 自定義filter配置 -->
<property name="filters">
<map>
<!-- 將自定義 的FormAuthenticationFilter注入shiroFilter中-->
<entry key="authc" value-ref="formAuthenticationFilter" />
</map>
</property>
複製程式碼
在我們的Controller新增驗證碼錯誤的異常判斷,從我們的Controller就可以發現,為什麼我們要把錯誤資訊存放在request域物件shiroLoginFailure,因為我們得在Controller中獲取獲取資訊,從而給使用者對應的提示
@RequestMapping("login")
public String login(HttpServletRequest request)throws Exception{
//如果登陸失敗從request中獲取認證異常資訊,shiroLoginFailure就是shiro異常類的全限定名
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
//根據shiro返回的異常類路徑判斷,丟擲指定異常資訊
if(exceptionClassName!=null){
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
//最終會拋給異常處理器
throw new CustomException("賬號不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new CustomException("使用者名稱/密碼錯誤");
} else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("驗證碼錯誤 ");
}else {
throw new Exception();//最終在異常處理器生成未知錯誤
}
}
//此方法不處理登陸成功(認證成功),shiro認證成功會自動跳轉到上一個請求路徑
//登陸失敗還到login頁面
return "login";
}
複製程式碼
<TR>
<TD>驗證碼:</TD>
<TD><input id="randomcode" name="randomcode" size="8" /> <img
id="randomcode_img" src="${baseurl}validatecode.jsp" alt=""
width="56" height="20" align='absMiddle' /> <a
href=javascript:randomcode_refresh()>重新整理</a></TD>
</TR>
複製程式碼
五、記住我
Shiro還提供了記住使用者名稱和密碼的功能!
使用者登陸選擇“自動登陸”本次登陸成功會向cookie寫身份資訊,下次登陸從cookie中取出身份資訊實現自動登陸。
想要實現這個功能,我們的認證資訊需要實現Serializable介面。
public class ActiveUser implements java.io.Serializable {
private String userid;//使用者id(主鍵)
private String usercode;// 使用者賬號
private String username;// 使用者名稱稱
private List<SysPermission> menus;// 選單
private List<SysPermission> permissions;// 許可權
}
複製程式碼
5.1配置rememeber管理器
<!-- rememberMeManager管理器,寫cookie,取出cookie生成使用者資訊 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie" />
</bean>
<!-- 記住我cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- rememberMe是cookie的名字 -->
<constructor-arg value="rememberMe" />
<!-- 記住我cookie生效時間30天 -->
<property name="maxAge" value="2592000" />
</bean>
複製程式碼
注入到安全管理器類上
<!-- securityManager安全管理器 -->
<bean id="securityManager"~~~····
<property name="cacheManager" ref="cacheManager"/>
<!-- 注入session管理器 -->
<property name="sessionManager" ref="sessionManager" />
<!-- 記住我 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
複製程式碼
配置頁面的input名稱:
<tr>
<TD></TD>
<td><input type="checkbox" name="rememberMe" />自動登陸</td>
</tr>
複製程式碼
如果設定了“記住我”,那麼訪問某些URL的時候,我們就不需要登陸了。將記住我即可訪問的地址配置讓UserFilter攔截。
<!-- 配置記住我或認證通過可以訪問的地址 -->
/index.jsp = user
/first.action = user
/welcome.jsp = user
複製程式碼
六、總結
- Shiro的授權過程和認證過程是類似的,在配置檔案上配置需要授權的路徑,當訪問路徑的時候,Shiro過濾器去找到reaml,reaml返回資料以後進行比對。
- Shiro支援註解式授權,直接在Controller方法上使用註解宣告訪問該方法需要授權
- Shiro還支援標籤授權,但一般很少用
- 由於每次都要對reaml查詢資料庫,效能會低。Shiro預設是支援授權快取的。為了達到很好的效果,我們使用Ehcache來對Shiro的快取進行管理
- 配置會話管理器,對會話時間進行控制
- 手動清空快取
- 由於驗證使用者名稱和密碼之前,一般需要驗證驗證碼的。所以,我們要改寫表單驗證的功能,先讓它去看看驗證碼是否有錯,如果驗證碼有錯的話,那麼使用者名稱和密碼就不用驗證了。
- 將自定義的表單驗證類配置起來。
- 使用Shiro提供的記住我功能,如果使用者已經認證了,那就不用再次登陸了。可以直接訪問某些頁面。
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y