什麼是權限管理?
權限管理是系統的安全範疇,要求必須是合法的用戶纔可以訪問系統(用戶認證),且必須具有該 資源的訪問權限纔可以訪問該 資源(授權)。
認證:對用戶合法身份的校驗,要求必須是合法的用戶纔可以訪問系統。
授權:訪問控制,必須具有該 資源的訪問權限纔可以訪問該 資源。
權限模型:標準權限數據模型包括 :用戶、角色、權限(包括資源和權限)、用戶角色關係、角色權限關係。
權限分配:通過UI界面方便給用戶分配權限,對上邊權限模型進行增、刪、改、查操作。
權限控制:
基於角色的權限控制:根據角色判斷是否有操作權限,因爲角色的變化 性較高,如果角色修改需要修改控制代碼,系統可擴展性不強。
基於資源的權限控制:根據資源權限判斷是否有操作權限,因爲資源較爲固定,如果角色修改或角色中權限修改不需要修改控制代碼,使用此方法系統可維護性很強。建議使用。
權限管理的解決方案:
對於粗顆粒權限管理,建議在系統架構層面去解決,寫系統架構級別統一代碼(基礎代碼)。
粗顆粒權限:比如對系統的url、菜單、jsp頁面、頁面上按鈕、類方法進行權限管理,即對資源類型進行權限管理。
對於細顆粒權限管理:
粗顆粒權限:比如用戶id爲001的用戶信息(資源實例)、類型爲t01的商品信息(資源實例),對資源實例進行權限管理,理解對數據級別的權限管理。
細顆粒權限管理是系統的業務邏輯,業務邏輯代碼不方便抽取統一代碼,建議在系統業務層進行處理。
基於url的權限管理(掌握):
企業開發常用的方法,使用web應用中filter來實現,用戶請求url,通過filter攔截,判斷用戶身份是否合法(用戶認證),判斷請求的地址是否是用戶權限範圍內的url(授權)。
shiro:
shiro是一個權限管理框架,是apache下的開源項目。相比spring security框架更簡單靈活,spring security對spring依賴較強。shiro可以實現web系統、c/s、分佈式等系統 權限管理。
shiro認證流程:重要
1、subject(主體)請求認證,調用subject.login(token)
2、SecurityManager (安全管理器)執行認證
3、SecurityManager通過ModularRealmAuthenticator進行認證。
4、ModularRealmAuthenticator將token傳給realm,realm根據token中用戶信息從數據庫查詢用戶信息(包括身份和憑證)
5、realm如果查詢不到用戶給ModularRealmAuthenticator返回null,ModularRealmAuthenticator拋出異常(用戶不存在)
6、realm如果查詢到用戶給ModularRealmAuthenticator返回AuthenticationInfo(認證信息)
7、ModularRealmAuthenticator拿着AuthenticationInfo(認證信息)去進行憑證(密碼 )比對。如果一致則認證通過,如果不致拋出異常(憑證錯誤)。
subject:主體
Authenticator:認證器( shiro提供)
realm(一般需要自定義):相當於數據源,認證器需要realm從數據源查詢用戶身份信息及權限信息。
授權流程
1、對subject進行授權,調用方法isPermitted("permission串")
2、SecurityManager執行授權,通過ModularRealmAuthorizer執行授權
3、ModularRealmAuthorizer執行realm(自定義的CustomRealm)從數據庫查詢權限數據
調用realm的授權方法:doGetAuthorizationInfo
4、realm從數據庫查詢權限數據,返回ModularRealmAuthorizer
5、ModularRealmAuthorizer調用PermissionResolver進行權限串比對
6、如果比對後,isPermitted中"permission串"在realm查詢到權限數據中,說明用戶訪問permission串有權限,否則 沒有權限,拋出異常。
使用maven整合shiro和ssmweb.xml中的配置
額外添加
<!-- 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>
可以查看官方文檔
pom.xml中需要添加的配置
<!--使用shiro需要的包 -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
自定義spring-shiro.xml即網上說的application-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--注入自定義的realm -->
<property name="realm" ref="myRealm"/>
<!-- 注入session管理器 -->
<property name="sessionManager" ref="sessionManager" />
<!-- 注入緩存管理器 -->
<property name="cacheManager" ref="cacheManager"/>
<!-- 記住我 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- 自定義Realm -->
<bean id="myRealm" class="com.csl.realm.MyRealm">
<!--將憑證匹配器設置到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="2"/>
</bean>
<!-- ================================ -->
<!-- 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>
<!-- ================================ -->
<!--緩存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:resources/shiro-ehcache.xml"/>
</bean>
<!-- ================================ -->
<!-- 會話管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效時長,單位毫秒 -->
<property name="globalSessionTimeout" value="600000"/>
<!-- 刪除失效的session -->
<property name="deleteInvalidSessions" value="true"/>
</bean>
<!-- Shiro過濾器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,這個屬性是必須的 -->
<property name="securityManager" ref="securityManager"/>
<!-- 身份認證失敗,則跳轉到登錄頁面的配置 -->
<property name="loginUrl" value="/login.jsp"/>
<!-- 權限認證失敗,則跳轉到指定頁面 -->
<property name="unauthorizedUrl" value="/unauthor.jsp"/>
<!-- 自定義filter配置 比如驗證碼,要在認證之前-->
<!--
<property name="filters">
<map>
將自定義 的FormAuthenticationFilter注入shiroFilter中
<entry key="authc" value-ref="formAuthenticationFilter"/>
</map>
</property>
-->
<!-- Shiro連接約束配置,即過濾鏈的定義 -->
<property name="filterChainDefinitions">
<value>
/index.jsp=anon
/login.jsp=anon
/admin*=authc
/role*=anon
/login.do= roles[admin],perms["user:view"]
</value>
</property>
</bean>
<!-- 保證實現了Shiro內部lifecycle函數的bean執行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 開啓Shiro註解 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--開啓aop對類代理 -->
<aop:config proxy-target-class="true"></aop:config>
</beans>
緩存管理器還得需要導入shiro-ehcache.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <ehcache name="es">
- <diskStore path="java.io.tmpdir"/>
- <!--
- name:緩存名稱。
- maxElementsInMemory:緩存最大數目
- maxElementsOnDisk:硬盤最大緩存個數。
- eternal:對象是否永久有效,一但設置了,timeout將不起作用。
- overflowToDisk:是否保存到磁盤,當系統當機時
- timeToIdleSeconds:設置對象在失效前的允許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。
- timeToLiveSeconds:設置對象在失效前允許存活時間(單位:秒)。最大時間介於創建時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。
- diskPersistent:是否緩存虛擬機重啓期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
- diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩衝區。
- diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
- memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你可以設置爲FIFO(先進先出)或是LFU(較少使用)。
- clearOnFlush:內存數量最大時是否清除。
- memoryStoreEvictionPolicy:
- Ehcache的三種清空策略;
- FIFO,first in first out,這個是大家最熟的,先進先出。
- LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,緩存的元素有一個hit屬性,hit值最小的將會被清出緩存。
- LRU,Least Recently Used,最近最少使用的,緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。
- -->
- <defaultCache
- maxElementsInMemory="10000"
- eternal="false"
- timeToIdleSeconds="120"
- timeToLiveSeconds="120"
- overflowToDisk="false"
- diskPersistent="false"
- diskExpiryThreadIntervalSeconds="120"
- />
- <!-- 登錄記錄緩存鎖定10分鐘 -->
- <cache name="passwordRetryCache"
- maxEntriesLocalHeap="2000"
- eternal="false"
- timeToIdleSeconds="3600"
- timeToLiveSeconds="0"
- overflowToDisk="false"
- statistics="true">
- </cache>
- </ehcache>
登錄驗證原理
登陸原理
使用FormAuthenticationFilter過慮器實現 ,原理如下:
將用戶沒有認證時,請求loginurl進行認證,用戶身份和用戶密碼提交數據到loginurl
FormAuthenticationFilter攔截住取出request中的username和password(兩個參數名稱是可以配置的)
FormAuthenticationFilter調用realm傳入一個token(username和password)
realm認證時根據username查詢用戶信息(在Activeuser中存儲,包括 userid、usercode、username、menus)。
如果查詢不到,realm返回null,FormAuthenticationFilter向request域中填充一個參數(記錄了異常信息)
由於FormAuthenticationFilter的用戶身份和密碼的input的默認值(username和password),修改頁面的賬號和密碼 的input的名稱爲username和password(可以更改)
使用PermissionsAuthorizationFilter
在spring-shiro.xml中配置url所對應的權限。
測試流程:
1、在spring-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返回的權限列表中,授權通過。
==============================================
shiro的過慮器
過濾器簡稱 |
對應的java類 |
anon |
org.apache.shiro.web.filter.authc.AnonymousFilter |
authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port |
org.apache.shiro.web.filter.authz.PortFilter |
rest |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles |
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl |
org.apache.shiro.web.filter.authz.SslFilter |
user |
org.apache.shiro.web.filter.authc.UserFilter |
logout |
org.apache.shiro.web.filter.authc.LogoutFilter |
anon:例子/admins/**=anon 沒有參數,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要認證(登錄)才能使用,FormAuthenticationFilter是表單認證,沒有參數
perms:例子/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每個參數都通過才通過,想當於isPermitedAll()方法。
user:例如/admins/user/**=user沒有參數表示必須存在用戶, 身份認證通過或通過記住我認證通過的可以訪問,當登入操作時不做檢查
註解的方式 @RequiresPermissions("user:view")
jsp標籤
Jsp頁面添加:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>
標籤名稱 |
標籤條件(均是顯示標籤內容) |
<shiro:authenticated> |
登錄之後 |
<shiro:notAuthenticated> |
不在登錄狀態時 |
<shiro:guest> |
用戶在沒有RememberMe時 |
<shiro:user> |
用戶在RememberMe時 |
<shiro:hasAnyRoles name="abc,123" > |
在有abc或者123角色時 |
<shiro:hasRole name="abc"> |
擁有角色abc |
<shiro:lacksRole name="abc"> |
沒有角色abc |
<shiro:hasPermission name="abc"> |
擁有權限資源abc |
<shiro:lacksPermission name="abc"> |
沒有abc權限資源 |
<shiro:principal> |
顯示用戶身份名稱 |
<shiro:principal property="username"/> 顯示用戶身份中的屬性值
<shiro:hasPermission name="user:view">
<input type="button" value="有權限"/>
</shiro:hasPermission>
授權測試
當調用controller的一個方法,由於該 方法加了@RequiresPermissions("item:query") ,shiro調用realm獲取數據庫中的權限信息,看"item:query"是否在權限數據中存在,如果不存在就拒絕訪問,如果存在就授權通過。
當展示一個jsp頁面時,頁面中如果遇到<shiro:hasPermission name="item:update">,shiro調用realm獲取數據庫中的權限信息,看item:update是否在權限數據中存在,如果不存在就拒絕訪問,如果存在就授權通過。
問題:只要遇到註解或jsp標籤的授權,都會調用realm方法查詢數據庫,需要使用緩存解決此問題。
shiro緩存
爲了避免頻繁的授權查詢數據庫,使用shiro緩存
緩存流程
shiro中提供了對認證信息和授權信息的緩存。shiro默認是關閉認證信息緩存的,對於授權信息的緩存shiro默認開啓的。主要研究授權信息緩存,因爲授權的數據量大。
用戶認證通過。
該 用戶第一次授權:調用realm查詢數據庫
該 用戶第二次授權:不調用realm查詢數據庫,直接從緩存中取出授權信息(權限標識符)。
使用maven座標<dependency><groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
導入jar包 共有兩個 有依賴關係
緩存清空
如果用戶正常退出,緩存自動清空。
如果用戶非正常退出,緩存自動清空。
如果修改了用戶的權限,而用戶不退出系統,修改的權限無法立即生效。
需要手動進行編程實現:
在權限修改後調用realm的clearCache方法清除緩存。
在service中,權限修改後調用realm的方法。
在realm中定義clearCached方法:
//清除緩存
public void clearCached() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
在service層首先要注入realm 調用realm中的方法清除緩存實現記住我功能
先首要將身份實現serializable接口
配置rememberMeManager
登錄頁面的 input標籤的(type="chheckbox")name屬性也要爲rememberMe