概述:
上一篇文章中我們介紹的權限控制方案的實現方式是通過URL攔截實現的,這裏介紹通過shiro實現權限控制。shiro是apache下的開源的權限管理框架。
Shiro架構:
Subject:主體,是用戶和程序的統一抽象。
SecurityManager:安全管理器,認證和授權的核心處理類。
Authenticator:認證器,主體認證的處理類。
Authorizer:授權器,主體授權的處理類。
SessionManager:shiro提供的一套session管理機制。
SessionDao:對於session的存儲需要用到sessionDao。
CacheManager;緩存管理器,主要對session和相關數據進行緩存管理。
Realm:通過realm間接連接數據源,認證和授權需要通過realm存取認證和授權數據。
Cryptography:密碼管理器,shiro提供的一套加密解密組建。
整合shiro
實現方式:
1、通過shiro默認的IniRealm實現(開發中一般不用該方式),該方式需要將認證和授權信息配置在一個ini文件中,shiro會默認通過IniRealm讀取該文件信息,實現認證。
2、通過自定義Realm實現(一般都需要自定義Realm),自定義的Realm可以自定義認證和授權信息獲取的方式(例如從數據庫中查詢),自定義的Realm繼承AuthorizingRealm。
這裏主要介紹通過自定義Realm實現認證和授權的方式。下面通過將shiro整合到項目中的過程來介紹shiro的基本用法和shiro的工作原理。
整合過程:
1、添加jar包
主要包括:shiro和spring整合的jar包,shiro和web應用整合的jar包和shiro一些基本的包。
2、配置shiro的filter
上篇文章我們介紹通過URL攔截中用到了攔截器,shiro內部也是用到了filter攔截進行實現。
配置信息:
<!-- 將shiroFilter作爲spring的Bean,由Spring來管理該Filter,實現和其他Filter的統一 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 設置true表示由servlet容器控制filter的生命週期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 設置spring容器filter指定的Bean的id,如果不設置則找與filter-name一致的bean-->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3、applicationContext-shiro.xml
在applicationContext-shiro.xml中配置shiroFilter需要的Bean及其他相關配置。
<!-- web.xml中shiro的filter對應的bean -->
<!-- Shiro 的Web過濾器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 -->
<property name="loginUrl" value="/login.action" />
<!-- 認證成功跳轉到first.action-->
<property name="successUrl" value="/first.action"/>
<!-- 指定沒有權限操作時跳轉頁面-->
<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- 自定義filter配置 -->
<property name="filters">
<map>
<!-- 將自定義 的FormAuthenticationFilter注入shiroFilter中-->
<entry key="authc" value-ref="formAuthenticationFilter" />
</map>
</property>
<!-- 過慮器鏈定義,從上向下順序執行,一般將/**放在最下邊 -->
<property name="filterChainDefinitions">
<value>
<!-- 對靜態資源設置匿名訪問 -->
/images/** = anon
/js/** = anon
/styles/** = anon
/validatecode.jsp = anon
<!-- 只需請求logout.action地址即可清除session-->
/logout.action = logout
<!--授權配置,例queryItems.action請求需要item:query權限-->
/items/queryItems.action = perms[item:query]
/items/editItems.action = perms[item:edit]
<!-- /** = authc 所有url都必須認證通過纔可以訪問-->
/** = authc
</value>
</property>
</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>
<!-- 自定義的realm -->
<bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
<!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 -->
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!-- 憑證匹配器,後邊有介紹 -->
<bean id="credentialsMatcher"
class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="1" />
</bean>
<!-- 緩存管理器,後邊將會介紹關於引入ehcache緩存 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>
<!--session管理器,讓shiro對session管理-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效時長,單位毫秒 -->
<property name="globalSessionTimeout" value="600000"/>
<!-- 刪除失效的session -->
<property name="deleteInvalidSessions" value="true"/>
</bean>
<!-- 自定義form認證過慮器 -->
<!-- 基於Form表單的身份驗證過濾器,不配置將採用默認值,默認值爲username和password -->
<bean id="formAuthenticationFilter"
class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter ">
<!-- 表單中賬號的input名稱 -->
<property name="usernameParam" value="username" />
<!-- 表單中密碼的input名稱 -->
<property name="passwordParam" value="password" />
</bean>
4、認證:
實現原理:由上邊的配置可知,當用戶進行認證時,會去請求loginurl進行認證,中間FormAuthenticationFilter進行攔截,取出request中攜帶的username和password參數(注:這裏默認爲username和password,也可以自行配置),FormAuthenticationFilter會調用realm(自定義的嗎?)進行認證,同時傳給realm一個token參數,username和password封裝在token中,realm會根據username查詢用戶信息,如果查詢到則返回,如果查詢不到則會返回null,這時FormAuthenticationFilter會向request中設置異常參數和信息。
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//token中封裝了用戶輸入的用戶名和密碼,從token中取出用戶名
String userCode = (String) token.getPrincipal();
//根據用戶輸入的userCode從數據庫查詢
SysUser sysUser = null;
try {
sysUser = sysService.findSysUserByUserCode(userCode);
} catch (Exception e1) {
e1.printStackTrace();
}
// 如果查詢不到返回null
if(sysUser==null){
//此處返回null,ModularRealmAuthenticator拋出UnknownAccountException(用戶不存在)異常
return null;
}
//獲取用戶散列密碼
String password = sysUser.getPassword();
//獲取鹽
String salt = sysUser.getSalt();
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(sysUser.getId());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setUsername(sysUser.getUsername());
List<SysPermission> menus = null;
try {
//根據用戶id取出菜單
menus = sysService.findMenuListByUserId(sysUser.getId());
} catch (Exception e) {
e.printStackTrace();
}
//用戶菜單設置到activeUser
activeUser.setMenus(menus);
//將activeUser設置到simpleAuthenticationInfo返回
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password,ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
}
5、登出
對於登出可以不用去實現具體的退出操作,只需要訪問一個指定的登出的url,因爲中間會由LogoutFilter攔截,同時清除session數據。配置方式見applicationContext-shiro.xml中的logout配置
6、授權配置
說明:對於shiro的授權配置有三種方法:
1、通過配置文件進行配置。
2、通過註解進行配置。
3、通過jsp標籤進行配置。
對於配置文件的配置方式見applicationContext-shiro.xml中的授權配置部分。
實現原理爲:在用戶請求到/users/deleteUser.action時會被PermissionsAuthorizationFilter攔截,filter根據配置發現需要“item:query”權限,這時PermissionAuthorizationFilter會調用realm從數據庫中獲取權限信息,如果發現用戶擁有該權限會放行,如果發現沒有會轉到前邊配置中指定的refuse.jsp頁面。
7、權限判斷
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
//獲取身份信息
ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
List<SysPermission> permissionList = null;
try {
//獲取用戶擁有的權限信息
permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
} catch (Exception e) {
e.printStackTrace();
}
List<String> permissions = new ArrayList<String>();
if(permissionList!=null){
for(SysPermission sysPermission:permissionList){
permissions.add(sysPermission.getPercode());
}
}
//返回授權信息,將查詢到的權限信息設置到simpleAuthorizationInfo中
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
}
8、設置憑證匹配器
由於在數據庫中存放的密碼是經過加密算法的散列值,所以在shiro也需要對取到的密碼進行相同的加密算法才能進行密碼比對,這裏通過在配置文件中配置的憑證匹配器對加密算法進行配置,對於憑證匹配器的配置見applicationContext-shiro.xml。
上邊對於授權的方式我們使用的是通過配置文件進行實現的,但是這樣的缺點在,URL和對應的權限成對配置,這樣將造成很大的配置量,相對來說比較麻煩;下邊我們說明一下如何通過註解的方式進行配置。
整合改進
註解方式實現授權:
上邊介紹了對於shiro的授權配置有三種方式,上邊的方式是通過配置文件的方式實現的,下邊介紹一下如何通過註解方式和jsp標籤方式進行授權配置。
開啓註解:
由於註解方式內部使用了spring的AOP方式,所以需要首先開啓spring的AOP,同時開啓shiro的註解支持。配置如下:
<!-- 開啓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>
註解使用:
註解的使用方式爲,在需要進行權限控制的方法上添加相應註解,註解的寫法爲:
@RequiresPermissions(“users:delete”),表示執行此方法需要“users:delete”權限。
Jsp標籤實現授權:
對於通過jsp標籤實現授權需要引入shiro的自定義標籤
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
標籤使用:shiro提供的標籤很多,這裏介紹其中幾個:
1、認證成功後可以顯示的內容放在<shiro:authenticated>內。
2、當用戶有指定角色時才顯示的內容放在<shiro:hasRole name="角色名">
內
3、當用戶有指定的權限時才顯示的內容放在<shiro:hasPermission name="item:query">內
在實際應用當中,通過註解和jsp標籤的方式相對多些。
引入緩存:
對於上邊的使用,無論是通過何種方式配置的權限,存在一個較大的問題是,對於權限的判斷,每次都需要realm從數據庫中查詢權限信息進行比對,這樣對系統的性能消耗是很大的,爲了解決該問題,我們爲shiro引入緩存處理,這樣就只需要在用戶第一次訪問時去查詢數據庫,再後邊就可以直接從緩存中取,這裏使用的是ehcache。
1、添加ehcache的jar包。
2、配置cacheManager,需要將cacheManager注入到securityManager中,對於相關配置見applicationContext-shiro.xml。
3、創建shiro-ehcache.xml,定義ehcache的使用方案,具體配置如下:
<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>
其他
shiro提供了在用戶退出後緩存的自動清空處理,然而,在用戶登錄期間對用戶的緩存進行更新處理,需要調用realm的clearCached方法對緩存進行清空。
總結:
對於利用shiro對權限的控制和前邊基於URL攔截的控制進行對比來說,shiro的控制功能更加強大一些,但是對於框架的依賴性大些,雖然shiro本身是不依賴與其他框架的,但是這裏和項目整合還是依賴了spring框架,在使用shiro時如果使用註解的方式進行權限控制會更加方便和直觀。這裏主要介紹了shiro和項目整合中的基本用法,其中對於認證和授權的流程在後邊進行介紹,這些只是shiro的一些基本用法,對於shiro的其他應用特點還有待繼續瞭解和學習,也希望這篇文章可以對你有所幫助吧。