javascript
在Spring MVC中使用Apache Shiro安全框架
我們在這里將對一個集成了Spring MVC+Hibernate+Apache Shiro的項目進行了一個簡單說明。這個項目將展示如何在Spring MVC 中使用Apache Shiro來構建我們的安全框架。
[TOC]
閱讀文章前,您需要做以下準備:
- Maven 3環境
- Mysql-5.6+
- JDK1.7+
- git環境
- git.oschina.net帳號
- Apache Tomcat 7+
- 您熟練掌握的編輯工具,推薦使用InterlliJ IDEA 14+
開始
項目地址git.oschina.net
項目地址github.com
安全管理框架數據結構
首先,我們在mysql數據庫中創建schema,命名為shirodemo。我們在創建兩個用戶shiroDemo@localhost和shiroDemo@%,這里我們將用戶的密碼簡單設置成123456。
然后,我們將項目從git服務器上clone到本地后,我們可以在項目根目錄下的resources中發現db.sql文件。這個文件是項目的數據庫結構文件,你可以將db.sql導入到數據庫shirodemo中。
我們這里的權限結構設計比較簡單,我們以表格的形式說明主要數據庫結構:
**Table:t_user**
| id | int | 11 | 用戶表的主鍵 |
| password | varchar | 255 | 密碼 |
| username | varchar | 255 | 用戶名,全局唯一,shiro將使用用戶名來鎖定安全數據中的用戶數據。 |
**Table:t_role**
| id | int | 11 | 主鍵 |
| rolename | varchar | 255 | 角色名稱,全局唯一。shiro將通過角色名來進行鑒權 |
**Table:t_permission**
| id | int | 11 | 主鍵 |
| role_id | int | 11 | 關聯role的外鍵 |
| dataDomain | varchar | 255 | 系統數據模型的域(自己定義的概念,下面我們將會介紹) |
| dataType | varchar | 255 | permission對應的系統實例的類型 |
| operation | varchar | 255 | permession許可的操作,例如add,del等等 |
**Table:t_authc_map**
| id | int | 11 | 主鍵 |
| authcType | varchar | 255 | 驗證類型,枚舉:anon,authc,perms,roles |
| url | varchar | 255 | 系統資源的url |
| val | varchar | 255 | 具體的權限字符串,例如:user:query |
t_user和t_role表就不用詳細介紹了,就是系統的用戶表和角色表。它與t_role角色表的關系是多對多的關系,即一個用戶可以有多個角色,一個角色可以包含多個用戶。
那么我們介紹一下t_permission表,這個表存放的數據是角色擁有的permission(這里我們就用shiro的permission概念,不翻譯了。因為翻譯過來是許可,但是許可二字還不能完全闡釋permission的概念)。每一個role會對應一個permission,即一對一的關系。
表t_authc_map存儲的是Shiro filter需要的配置數據,這些數據組合起來,定義了訪問控制(Access Controll)的規則,即定義了哪些url可以被擁有哪些permission或者擁有哪些role的用戶訪問。在我們這個例子中,其實這張表用處不大。當初設計這樣一張表的目的是,能夠動態管理訪問控制的規則,但是并不能。
提示: 訪問控制規則的數據是在Spring bean初始化時就加載給了訪問控制的filter。我們試想一下,在你的webapp運行時(runtime),我們可以通過一些手段來修改系統的訪問控制規則,那么勢必會造成用戶提交事務時的處理變得非常復雜。例如,用戶正在訪問一個url連接,我們通過后臺修改了url的訪問控制權限,這時這個用戶已經提交了一次事務操作,那么怎么判斷這次提交是否合法呢?要把這個問題處理清楚就很復雜。那么你可能會問,如果我為系統增加了一個模塊,模塊中有一些新建的url需要提供給用戶訪問,但是我不想重啟我的應用,直接在數據庫中配置完成,怎么辦?我想,既然增加了模塊,當然需要重新部署,那么僅通過配置數據完成部署,我感覺在現在Spring MVC下,很難實現。所以個人看法,訪問控制規則數據使用配置文件還是持久化到數據庫,沒有什么區別。但是本文中還是會介紹如何將訪問控制規則持久化到數據庫中。
shiroTest模塊
我們可以看見項目中有一個shiroTest模塊,這個模塊中主要實現在單元測試時,使用的通用程序。在本例中,我們在shiroTest模塊中實現一個proxool數據源,為其他模塊在單元測試時提供數據庫連接。
請注意,我們這里配置的數據源,僅提供給單元測試使用。而我們的webapp中將使用Spring 的JNDI數據源。為什么這么做呢?主要原因是:本例中我們使用的是Tomcat做為中間件,但是實際項目的生產環境,可能使用商業中間件,例如Weblogic等等。那么我們在遷移過程中,就不用考慮中間件使用的是什么數據源,只去調用中間件JNDI上綁定的數據源名稱就可以了。而且這些商業中間件一般都有很好的數據源管理功能。如果我們使用獨立的數據源,那么數據源就脫離的中間件的管理,豈不是功能浪費?
我們在test中,實現一個測試用例,這個測試用例主要測試數據源的連接:
| 1 2 3 4 5 6 7 8 | public void testApp() throws SQLException { ????????ApplicationContext cxt = new ClassPathXmlApplicationContext( ????????????????"classpath*:conf/*-beans.xml"); ????????DataSource ds= (DataSource) cxt.getBean("ds-default"); ????????Connection con=ds.getConnection(); ????????con.close(); ????????assertTrue(true); } |
我們在shiroTest項目根目錄下運行mvn test,測試一下。
base模塊
base模塊主要實現的是整個項目中,各個模塊公用的程序。其中包含了:
- Hibernate Session Factory
- Ehcache
- POJO Class
- BaseDao 所有dao的父類
- Hibernte 事務管理
authmgr模塊
authmgr模塊實現了如下功能
- 登錄
- 登出
- 查詢訪問控制規則數據
- 實現自定義Realm
- 實現Shiro的SecurityManager
authmgr模塊業務接口
我們來看一下接口com.ultimatech.shirodemo.authmgr.service.IAuthService
*com.ultimatech.shirodemo.authmgr.service.IAuthService*
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public interface IAuthService { ????/** ?????* 用戶登錄接口 ?????* @param userName 登錄用戶名 ?????* @param password 密碼 ?????* @throws AuthenticationException ?????*/ ????void logIn(String userName, String password) throws AuthenticationException; ????/** ?????* 用戶登出系統 ?????*/ ????void logOut(); ????/** ?????* 獲得數據庫中存儲的訪問控制數據 ?????* @return ?????*/ ????List<AuthcMap> getFilterChainDefinitions(); } |
自定義實現Realm
我們來看一下我們的Realm是如何實現:
*com.ultimatech.shirodemo.authmgr.realm.MyRealm*
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | ...... @Component("myRealm") public class MyRealm extends AuthorizingRealm { ????@Autowired ????public MyRealm(@Qualifier("shiroEncacheManager") CacheManager cacheManager) { ????????super(cacheManager); ????} ????@Autowired ????private IAuthDao dao; ???...... ????@Override ????protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { ????????//獲取登錄時輸入的用戶名 ????????String loginName = (String) principalCollection.fromRealm(getName()).iterator().next(); ????????//到數據庫查是否有此對象 ????????User user = this.getDao().findByName(loginName); ????????if (user != null) { ????????????//權限信息對象info,用來存放查出的用戶的所有的角色(role)及權限(permission) ????????????SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); ????????????//用戶的角色集合 ????????????info.setRoles(user.getRolesName()); ????????????//用戶的角色對應的所有權限,如果只使用角色定義訪問權限,下面的四行可以不要 ????????????List<Role> roleList = user.getRoleList(); ????????????for (Role role : roleList) { ????????????????info.addStringPermissions(role.getPermissionsString()); ????????????} ????????????return info; ????????} ????????return null; ????} ????@Override ????protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { ????????//UsernamePasswordToken對象用來存放提交的登錄信息 ????????UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; ????????//查出是否有此用戶 ????????User user = this.getDao().findByName(token.getUsername()); ????????if (user != null) { ????????????//若存在,將此用戶存放到登錄認證info中 ????????????return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); ????????} ????????return null; ????} } |
Shiro的SecurityManager
我們在Spring 容器中聲明一個名叫securityManager的bean。在resources/conf/authmgr-beans.xml中,我們看見如下代碼:
| 1 2 3 4 5 6 7 | <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> ????<!-- ref對應我們寫的realm? myRealm --> ????<property name="realm" ref="myRealm"/> ????<!-- 使用下面配置的緩存管理器 --> ????<property name="cacheManager" ref="shiroEncacheManager"/> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> |
由于我們很多模塊都會用到共享緩存,所以以上<property name="cacheManager" ref="shiroEncacheManager"/>中的shiroEncacheManager被定義在base模塊中。
我們可以去base模塊的目錄下找到resources/conf/base-beans.xml,找到如下代碼:
| 1 2 3 4 5 6 7 8 | <bean id="shiroEncacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> ????<property name="cacheManager" ref="ehCacheManager"/> </bean> <bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> ????<property name="configLocation" value="classpath:ehcache.xml"></property> ????<property name="shared" value="true"/> </bean> |
shiroWebapp模塊
shiroWebapp模塊是本例中的web應用。主要集成了Spring MVC框架、Hibernate框架,以及我們的安全框架Apache Shiro。
我們使用Shiro Filter來進行訪問控制,那么在web.xml文件中進行了如下配置:
| 1 2 3 4 5 6 7 8 | <filter> ????<filter-name>shiroFilter</filter-name> ????<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> ????<filter-name>shiroFilter</filter-name> ????<url-pattern>/*</url-pattern> </filter-mapping> |
我們使用Spring的DelegatingFilterProxy來創建Shiro Filter。<filter-name>shiroFilter</filter-name>這個參數要與Spring中Shiro Filter Bean的名字保持一致。在shiroWebapp下的resources/conf下的web-beans.xml文件中,我們可以看見Shiro Filter的配置:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <bean id="filterChainDefinitions" class="com.ultimatech.shirodemo.web.filter.ShiroFilterChainDefinitions"> ????<property name="filterChainDefinitions"> ????????<value> ????????????/html/**=anon ????????????/js/**=anon ????????????/css/**=anon ????????????/images/**=anon ????????????/authc/login=anon ????????????/login=anon ????????????<!--/user=perms[user:del]--> ????????????/user/add=roles[manager] ????????????/user/del/**=roles[admin] ????????????/user/edit/**=roles[manager] ????????????<!--/** = authc--> ????????</value> ????</property> </bean> <!-- 配置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地址,則跳到您訪問的地址 --> ????<property name="successUrl" value="/user"/> ????<!-- 如果您請求的資源不再您的權限范圍,則跳轉到/403請求地址 --> ????<property name="unauthorizedUrl" value="/html/403.html"/> ????<!-- 權限配置 --> ????<property name="filterChainDefinitionMap" ref="filterChainDefinitions" /> </bean> |
訪問控制數據
我們看見上面的filterChainDefinitions中,我們自定義了一個FacotryBean,這個bean主要實現將配置文件中的訪問控制數據和數據庫中的訪問控制數據整合在一起。(雖然我們之前已經說了,這兩種方式沒什么區別。)
*com.ultimatech.shirodemo.web.filter.ShiroFilterChainDefinitions*
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | public class ShiroFilterChainDefinitions implements FactoryBean<Ini.Section> { ????@Autowired ????private IAuthService authService; ????...... ????public static final String PREMISSION_STRING = "perms[{0}]"; ????public static final String ROLE_STRING = "roles[{0}]"; ????public Ini.Section getObject() throws Exception { ????????List<AuthcMap> list = this.getAuthService().getFilterChainDefinitions(); ????????Ini ini = new Ini(); ????????ini.load(this.getFilterChainDefinitions()); ????????Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME); ????????for (AuthcMap map : list) { ????????????String s = null; ????????????switch (AuthcType.valueOf(map.getAuthcType())) { ????????????????case roles: ????????????????????s = MessageFormat.format(ROLE_STRING, map.getVal()); ????????????????????break; ????????????????case perms: ????????????????????s = MessageFormat.format(PREMISSION_STRING, map.getVal()); ????????????????????break; ????????????????case authc: ????????????????????s = AuthcType.authc.name(); ????????????????case anon: ????????????????????s = AuthcType.anon.name(); ????????????????default: ????????????????????s = AuthcType.authc.name(); ????????????} ????????????section.put(map.getUrl(), s); ????????} ????????return section; ????} ???...... } |
關于訪問控制數據,我們要注意Shiro Filter在執行訪問控制時,是按訪問控制數據的順序來逐個驗證的,而我們將數據庫中的訪問控制數據追加到配置文件的后面。例如上面的配置:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <bean id="filterChainDefinitions" class="com.ultimatech.shirodemo.web.filter.ShiroFilterChainDefinitions"> ????<property name="filterChainDefinitions"> ????????<value> ????????????/html/**=anon ????????????/js/**=anon ????????????/css/**=anon ????????????/images/**=anon ????????????/authc/login=anon ????????????/login=anon ????????????/user/add=roles[manager] ????????????/user/del/**=roles[admin] ????????????/user/edit/**=roles[manager] ????????</value> ????</property> </bean> |
追加上我們在數據庫中的訪問配置數據:
| 1 | perms | /user | user:query |
| 2 | authc | /** | ? |
那么全部訪問控制數據應該是:
| 1 2 3 4 5 6 7 8 9 10 11 | /html/**=anon /js/**=anon /css/**=anon /images/**=anon /authc/login=anon /login=anon /user/add=roles[manager] /user/del/**=roles[admin] /user/edit/**=roles[manager] /user=perms[user:query] /**=authc |
這里我們要強調一下,如果把基于角色和基于permission的控制放在/**=authc之后的控制是不會起作用的。例如:
| 1 2 3 4 5 6 7 8 9 10 11 | /html/**=anon /js/**=anon /css/**=anon /images/**=anon /authc/login=anon /login=anon /user/add=roles[manager] /user/del/**=roles[admin] /user/edit/**=roles[manager] /**=authc /user=perms[user:query] |
這時如果用戶rose擁有user:del的permission,在他登錄系統以后也是可以訪問/user路徑的。其實我們想實現只有擁有user:query的用戶才能訪問/user,而rose擁有的是user:del。我們設置了/user=perms[user:query],而rose并沒有user:query這樣的permission,為什么rose還能訪問/user呢?這是因為,Shior Filter先使用訪問控制規則/**=authc對rose的權限進行了驗證,那么rose是一個已知身份的用戶,所以他可以訪問所有url,除了/**=authc之前設置的規則限制不能訪問的url。
是不是很混亂,一部分訪問控制規則在配置文件中,一部分又在數據庫中,而且訪問控制還有順序要求,一旦我們忽略任意一部分訪問控制數據,我們的設置就很難達到我們預期的效果。所以,將訪問控制數據分開并不是一個好的實踐。
我們這里實現了使用數據庫配置訪問控制數據,僅僅是為了開闊一下思路,并不推薦同時使用數據庫配置和配置文件配置。
關于permission語法
我們可以參考Understanding Permissions in Apache Shiro。你可能會發現好長的一篇文章啊!
那么下面我就我個人的理解,簡單對permission語法說明一下。
在我們設計系統時,我們經過一系列的分析過程,會得到我們要實現的系統中存在哪些實體。例如,系統中存在用戶(user),工作流(workflow)等實體。我們對這些實體進行抽象化,構成我們系統的基礎模型。那么實體除了數據屬性,例如,用戶名(username),流程名稱(flowname)等,還具備一些功能(function)或者叫做方法(method)的特征。我們使用OOP的思想來設計系統,那么我們抽象出來的實體就是我們所說的實體類,這些實體類代表了很多實體對象,例如,user類中,實際包含了tom,jack和rose,這些用戶的一個子集就組成了一個數據域。那么我們的permission就是由系統實體和功能,以及一個數據域組成,格式就像這樣:實體:功能:數據域。
例如: perms[user:query:*]——表示允許查詢(query)用戶(user實體類)所有(*)對象的許可。 perms[user:query,add,del,update]——表示允許查詢(query)、添加(add)、刪除(del)和更新(update)用戶(user)所有對象的許可。 perms[user:query:jack,rose]——表示允許查詢(query)用戶(user)中jack和rose數據的許可。 perms[workflow:approve:order]——表示允許操作工作流程(workflow)中一個名叫order的流程的審批許可。
試驗
我們掌握了項目中集成Apache Shiro的方法后,將項目在IDEA中run或者debug起來。使用tom、jack和rose用戶登錄系統中,看看會有什么現象。當然用戶、角色和permission數據都在db.sql文件中,導入數據庫時已經一起導入了。你可以修改這些數據,以及他們之間的關系來體驗Shiro的安全框架。
總結
希望通過這個說明,能夠讓你了解在本項目中是如何集成Apache Shiro安全框架的。
在接下來的計劃中,我們將實現在集群環境中使用Apache Shiro。
原文出處:?holynull
from:?http://www.importnew.com/22908.html
總結
以上是生活随笔為你收集整理的在Spring MVC中使用Apache Shiro安全框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM 调优 —— GC 长时间停顿问题
- 下一篇: 锁Lock 那点事儿