day55_BOS專案_07
- 今天內容安排:
- 1、許可權概述(認證、授權)
- 2、常見的許可權控制的方式(URL攔截許可權控制、方法註解許可權控制)
- 3、許可權模組資料模型(許可權表、角色表、使用者表、角色許可權關係表、使用者角色關係表)
- 4、apache shiro框架入門
- 5、將shiro應用到bos專案中進行認證和授權
- 6、shiro提供的許可權控制方式(主要使用前三種方式)
- 6.1、URL攔截許可權控制(正常)
- 6.2、方法註解許可權控制(重點)
- 6.3、頁面標籤許可權控制(shiro標籤庫)
- 6.4、程式碼級別許可權控制(瞭解,很少用)
1、許可權概述(認證、授權)
- 系統提供了很多功能,並不是所有的使用者登入系統都可以操作這些功能。我們需要對使用者的訪問進行控制。
- 認證:系統提供的用於識別使用者身份的功能(通常是登入功能) --> 讓系統知道你是誰?
- 授權:系統提供的賦予使用者訪問某個功能的能力 --> 讓系統知道你能做什麼?
2、常見的許可權控制的方式
2.1、URL攔截許可權控制 --> 基於過濾器或者攔截器
URL攔截許可權控制 圖解如下:

2.2、方法註解許可權控制 --> 基於代理技術(給Action新增代理)
方法註解許可權控制 圖解如下:

詳解如下:
我們依舊以StaffAction為例: 上面的方式類似於:我們在我們的專案中給我們的Action(StaffAction)注入進來的service,不是真正的service實現類(StaffServiceImpl),而是該service的代理。 對於我們注入進來的service,若是用介面的實現類(StaffServiceImpl)來宣告,就會報錯,為什麼呢? 因為我們`真正注入給Action的是代理物件`,而這個代理物件跟真正的實現類(StaffServiceImpl)是沒有關係的(我們同一級別)。 對於我們注入進來的service,若是我們用介面(StaffService)來宣告,是沒有問題的,又為什麼呢? 因為這個代理類實現了這個介面(StaffService),即我們用介面(父類)來引用子類的物件,亦即多型(父類引用子類)。 說白了,我跟你父親有關係,跟你沒半毛錢關係。 話說回來,我們現在是為Action建立代理,進行許可權檢查。 之前,我(Action)是被動接收者,你們注入你們的代理物件進來給我使用,通過你們的代理進行事務管理等操作。 現在,我(Action)是主動創造者,我自己創造我自己的代理物件,通過我自己的代理進行許可權認證等操作。
3、許可權模組資料模型
- 一共涉及到5張表
- 使用者表:t_user
- 角色表:auth_role
- 許可權表:auth_function
- 角色許可權關係表(多對多):role_function
- 使用者角色關係表(多對多):user_role
許可權模組資料模型圖如下:

第一步:我們使用 PowerDesigner
通過 許可權控制.pdm檔案
生成 建表文件bos_qx.sql
,為了避免外來鍵名衝突,需要修改建表文件的外來鍵名稱和刪除生成的t_user表的語句(因為該表之前已經生成過了)。
第二步:再將建表文件拖入 Navicat for SQL/">MySQL
中生成資料庫中對應的5張表格。
第三步:我們再使用 MyEclipse中的Hibernate反轉引擎
生成 實體類檔案
和 對應的Hibernate對映檔案
。
4、apache shiro框架入門
- 官網:https://shiro.apache.org/
- Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
- Apache Shiro™ 是一個功能強大且易於使用的Java安全框架,可執行身份驗證,授權,加密和會話管理。 藉助Shiro易於理解的API,您可以快速輕鬆地保護任何應用程式 - 從最小的移動應用程式到最大的Web和企業應用程式。
- 提供的功能:
詳解如下:
Apache Shiro是一個強大而靈活的開源安全框架,它能夠乾淨利落地處理身份認證,授權,企業會話管理和加密。 以下是你可以用 Apache Shiro所做的事情: 1、驗證使用者 2、對使用者執行訪問控制,如: 判斷使用者是否擁有角色admin。 判斷使用者是否擁有訪問的許可權。 3、在任何環境下使用 Session API。例如:CS程式。 4、可以使用多個使用者資料源。例如:一個是oracle使用者庫,另外一個是mysql使用者庫。 5、單點登入(SSO)功能。 比如:登入淘寶後,可以直接登入天貓商城。 詳解連線: https://blog.csdn.net/cruise_h/article/details/51013597 https://blog.csdn.net/javaloveiphone/article/details/52439613 6、“Remember Me”服務,類似購物車的功能,shiro官方建議開啟。
-
shiro的程式執行流程圖:
詳解如下:
Application code:應用程式程式碼,開發人員編寫的程式碼 Subject:主體,當前使用者,Subject 是與程式進行互動的物件,可以是人也可以是服務或者其他,通常就理解為使用者!(某個人或者某個應用軟體) SecurityManager:安全管理器,shiro框架的核心物件,管理各個元件 Realm:類似於Dao,負責訪問安全資料(使用者資料、角色資料、許可權資料)
-
shiro的程式執行流程詳細圖:
5、將shiro應用到bos專案中進行認證和授權
第一步:第一步:bos專案中匯入shiro-all.jar包
這裡只介紹spring配置模式。
第二步:在web.xml中配置一個spring用於整合shiro的過濾器
<!-- 配置一個spring用於整合shiro的過濾器 --> <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配置檔案applicationContext.xml中配置一個bean,id必須和上面的過濾器名稱相同
<!-- 配置一個工廠bean,用於建立shiro框架用到的過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager"></property> <!-- 注入當前系統的登入頁面 --> <property name="loginUrl" value="/login.jsp"/> <!-- 注入成功頁面 --> <property name="successUrl" value="/index.jsp"/> <!-- 注入許可權不足提示頁面 --> <property name="unauthorizedUrl" value="/404.html"/> <!-- 注入URL攔截規則 --> <property name="filterChainDefinitions"> <value> /css/** = anon /images/** = anon /js/** = anon /login.jsp* = anon /validatecode.jsp* = anon /userAction_login.action = anon <!-- 匿名訪問 --> /page_base_staff.action = perms["staff"] <!-- 表示當前使用者在該頁面認證通過,但是還沒有授予許可權,不能訪問 --> /* = authc <!-- 要求當前使用者必須要驗證通過,才可以訪問 --> </value> </property> </bean>
第四步:在spring配置檔案applicationContext.xml中註冊安全管理器,為安全管理器注入realm
<!-- 配置安全管理器bean --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- 為安全管理器注入自定義的realm --> <property name="realm" ref="bosRealm"></property> </bean> <!-- 配置自定義的realm --> <bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>
第五步:realm(介面)可以有兩種方式得到:
方式一:我們可以通過用框架提供的。
方式二:自定義一個BOSRealm實現類。
方式一提供的實現類JdbcRealm訪問資料庫使用的是sql語句,而我們當前的專案使用的是Hibernate框架,使用該框架,我們最好不要再使用sql語句了,所以我們本案例使用方式二。模仿實現類JdbcRealm來寫我們自定義的實現類BOSRealm。示例程式碼如下:
package com.itheima.bos.shiro; import javax.annotation.Resource; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import com.itheima.bos.dao.IUserDao; import com.itheima.bos.domain.User; public class BOSRealm extends AuthorizingRealm { @Resource private IUserDao userDao; /** * 認證方法 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("執行了我們自定義的認證方法"); UsernamePasswordToken upToken = (UsernamePasswordToken) token; // 從令牌中獲取使用者名稱 String username = upToken.getUsername(); User user = userDao.findUserByUsername(username); if (user == null) { // 使用者名稱不存在 return null; } else { // 使用者名稱存在 String password = user.getPassword(); // 獲取資料庫中儲存的密碼 // 把上述密碼包裝成AuthenticationInfo認證資訊物件,讓shiro框架幫我們去和我們輸入的密碼比較 // AuthenticationInfo認證資訊物件是介面,需要使用其實現類 SimpleAuthenticationInfo簡單認證資訊物件 // 建立SimpleAuthenticationInfo簡單認證資訊物件 /** * 使用有3個引數的構造方法 *引數1:principal 簽名,作用:程式可以在任意位置獲取當前放入的物件,比如:我們可以把認證通過的使用者拿出來放到session中 *引數2:credentials 憑證,從資料庫中查詢出的密碼 *引數3:realmName 當前realm的名稱 */ SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, this.getClass().getSimpleName()); // 返回給安全管理器,由安全管理器負責比對資料庫中查詢出的密碼和頁面提交的密碼(在token中),若比對成功,不會丟擲異常,會修改Subject的狀態為“已認證” return info; } } /** * 授權方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } }
第六步:完善UserAction中的login()方法
/* * 使用者登入,使用shiro提供的方式進行認證 */ public String login() { // 判斷使用者輸入的驗證碼是否正確 // 先獲取我們自己生成的驗證碼 String key = (String) ServletActionContext.getRequest().getSession().getAttribute("key"); // 判斷使用者是否有輸入驗證碼和輸入的驗證碼是否和我生成的驗證碼是否相等 if (StringUtils.isNotBlank(checkcode) && checkcode.equals(key)) { // 說明驗證碼存在且正確 // 獲得當前使用者物件 Subject subject = SecurityUtils.getSubject(); // 狀態為“未認證” String password = model.getPassword(); password = MD5Utils.md5(password); // 構造一個使用者名稱密碼令牌 AuthenticationToken token = new UsernamePasswordToken(model.getUsername(), password); try { subject.login(token); } catch (UnknownAccountException e) { e.printStackTrace(); // 輸出異常 this.addActionError(this.getText("usernamenotfound")); // 設定錯誤資訊 return "login"; // 返回登入頁面 } catch (IncorrectCredentialsException e) { e.printStackTrace(); // 輸出異常 this.addActionError(this.getText("loginError")); // 設定錯誤資訊 return "login"; // 返回登入頁面 } // 獲取簡單認證資訊物件中儲存的User物件 User user = (User) subject.getPrincipal(); // 將User放入session域中 ServletActionContext.getRequest().getSession().setAttribute("loginUser", user); return "home"; } else { // 說明驗證碼錯誤,設定錯誤提示資訊,並跳轉至登入頁面 // this.addActionError("驗證碼錯誤"); // 在Struts2中,所有的訊息提示都是基於國際化的。 this.addActionError(this.getText("validatecodeError")); return "login"; } }
第七步:在自定義Realm類中編寫授權方法
/** * 授權方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 建立SimpleAuthenticationInfo簡單授權資訊物件,使用沒有引數的構造方法 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 為當前使用者授予staff許可權(硬編碼)--> perms["staff"] info.addStringPermission("staff"); // 為當前使用者授予staff角色(硬編碼)--> roles["staff"] info.addRole("staff"); // TODO 根據當前登入使用者查詢資料庫,獲取其對應的許可權資料 return info; }
注意:上述第七步的授權方法是什麼時候被呼叫的呢?
答:是我們訪問某個功能的時候,而這個功能我們通過spring配置檔案applicationContext.xml進行配置的。在沒有學習 快取
之前,我們每次訪問某個功能的時候,該授權方法都會被執行。我們後面的學習中會加入shiro的快取管理器。
6、shiro提供的許可權控制方式(主要使用前三種方式)
6.1、URL攔截許可權控制(正常)
通過spring配置檔案applicationContext.xml進行配置,如下圖所示:

就是使用shiro提供的URL攔截許可權控制。
6.2、方法註解許可權控制(重點)
第一步:在spring配置檔案中開啟shiro的註解支援
問題一:要強制使用cglib為Action建立代理物件,為什麼呢?

答:因為我們的Action都實現了ModelDriven介面,所以Spring框架會預設優先針對介面而給Action建立jdk代理,建立jdk代理是基於實現介面的 而該代理物件裡面只有一個方法getModel(),沒有我們需要的方法。
而建立cglib代理是基於繼承的,cglib會繼承當前的Action類,所以基於cglib建立的代理會繼承Action的方法,裡面有我們需要的方法。
<!-- 開啟shiro註解支援 --> <!-- 自動代理 --> <bean id="defaultAdvisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> <!-- 注入強制使用cglib為Action建立代理物件,因為我們的Action都實現了ModelDriven介面,所以Spring框架會預設優先針對介面而給Action建立jdk代理,而該代理物件裡面只有一個方法getModel() --> <!-- 而cglib是基於繼承的,jdk是基於實現介面的 ,cglib會繼承當前的Action去建立代理,所以cglib建立的代理會繼承Action的方法,裡面有我們需要的方法--> <property name="proxyTargetClass" value="true"></property> </bean> <!-- 切面類 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"/>
第二步:在Action中的方法上使用shiro的註解描述執行當前方法需要具有的許可權
/** * 批量刪除(邏輯刪除) * @return */ @RequiresPermissions(value="staff") // 執行當前方法需要staff許可權,我們要先在BOSRealm中授予當前使用者為staff許可權才行,否則丟擲異常 @RequiresRoles(value="abc")// 執行當前方法需要abc角色,我們先在BOSRealm中授予當前使用者為abc角色才行,否則丟擲異常 public String delete() { staffService.deleteBatch(ids); return "list"; }
問題二:出現了新的異常,為什麼呢?

出現問題原因如下圖所示:

解決問題方法如下:
第三步:我們需要修改BaseAction的構造方法,增加判別條件

第四步:在struts.xml中配置全域性異常捕獲,統一跳轉到許可權不足的頁面

使用方法註解許可權控制的方式,我們的工作量主要在各種Action上的各種方法上添加註解,工作量還是挺大的!
6.3、頁面標籤許可權控制(shiro標籤庫)
第一步:在jsp頁面中引入shiro的標籤
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
第二步:使用shiro的標籤根據當前使用者擁有的許可權動態展示頁面元素

使用頁面標籤許可權控制的方式,我們的工作量主要給各個便籤新增許可權控制,便籤很多啊,工作量也很大呀!
6.4、程式碼級別許可權控制(瞭解,很少用)

使用程式碼級別許可權控制的方式,我們需要給每一個Action方法加入這兩句程式碼,這樣對程式碼不好,即程式碼健壯性差!