--觀點:
學習任何的知識,我們首先要知道它是什么,然后通過是什么(定義)來分析它的作用、行為。從而圈定學習的范圍。我們將這個過程稱為,學習思路!!
1. Shiro概述
1.1. Shiro是什么
官方說明:
href="#what-is-apache-shiro-" What is Apache Shiro? Apache Shiro is a powerful and flexible open-source security framework that cleanly handles authentication, authorization, enterprise session management and cryptography. |
Shiro是一個非常強大的、易於使用的、開源的、權限框架。它包括了權限校驗、權限授予、會話管理、安全加密等組件。
1.2. 為什么需要使用Shiro(興趣點)
如果你是需要設計RBAC(Role Based Access Control)基礎系統,需要編寫大量用於權限控制的代碼時。那么你需要使用Shiro。因為Shiro已經將RBAC系統大量的代碼封裝好,可以減少我們大量的工作量。
如:頁面的顯示的HTML控件根據登錄用戶的權限不同而不同。使用Shiro可以輕松解決。
1.3. Shiro的下載
shiro的下載路徑:http://shiro.apache.org/download.html
--所有包的說明,根據列表的說明,下載我們需要的jar包
包名 |
Maven 坐標 |
說明 |
Not Recommended |
Includes all binary functionality for Shiro |
|
<dependency> |
Required in all environments. Slf4j's slf4j-api jar and one of its binding jars is required. commons-beanutils is required only if using INI config.
只要使用shiro必須的核心包,它依賴slf4j 和 commons-beanutils 以及還需要一個INI配置文件 |
|
<dependency> |
Enables support for web-based applications.
支持基於Web的應用 |
|
<dependency> |
||
<dependency> |
Enables Jasig CAS support. the Apache Shiro based buji-pac4j project. 對cas單點登錄框架的支持 |
|
<dependency> |
Enables Ehcache-based famework caching. 對echche緩存框架的支持 |
|
<dependency> |
||
<dependency> |
||
<dependency> |
Enables Google Guice integration.
guice 類似spring的ioc框架。 |
|
<dependency> |
Enables Quartz-based scheduling for Shiro native session validation.
quartz定時器框架 |
|
<dependency> |
1.4. 包說明
--Shiro常用包
|
1.5. Shiro結構圖
|
Authentication:權限校驗,每次操作校驗用戶是否有訪問權限
Authorization:授權,用戶登錄時,授予用戶對應的權限
Session Management:會話管理,用於記錄用戶的登錄狀態
Cryptography:加密,加密算法的實現(SHA、MD5)
web Support:對Web項目的支持,Shiro的標簽!!
2. Shiro入門
2.1. 訪問流程圖
我們知道學習任何的框架,都是從配置流程開始的。我們我們學習Shiro也是一樣。所以首先要了解Shiro的訪問流程。從而知道配置的配置,快速入門。
|
- 首先應用訪問(可以使用遠程調用,可以是Web請求等),Shiro通過一個Subject對象來標識當前訪問的身份。這句話告訴我們,第一次訪問的時候,Shiro肯定會創建一個Subject對象標簽當前請求(用戶)的身份。
- SecurityManger容器創建一個Subject對象驗證請求的參數,SecurityManager的作用是統一管理Subject。這句話意味着,一個SecurityManager對象管理多個Subject的對象。
- Subject通過SecurityManger獲得操作當前用戶的權限,在啟動的那一刻,SecurityManger就會加載shiro.ini權限配置文件,在用戶登錄成功后,可以根據shiro配置的信息,獲得用戶對應的權限。
- shiro配置:是一個權限控制信息文件,里面必須包括用戶的驗證信息,權限的信息
--登錄流程!!!!
2.2. 配置步驟說明
根據訪問流程圖,我們要使用訪問Shiro權限框架的功能。首先需要有一個配置文件shiro.ini配置文件配置了用戶的權限認證信息。然后通過SessionManager對象讀取配置文件,獲得用戶身份Subject,
客戶端代碼通過當前用戶訪問有權限的操作。
由此,得出配置步驟:
第一步:任何框架都需要導入包
第二步:創建一個shiro.ini配置文件。(文件名任意編寫,后綴必須為ini)
第三步:編寫測試代碼
2.3. 配置步驟
注意事項:shiro框架依賴slf4j以及beanutils包。
2.3.1. 第一步:導入包
|
2.3.2. 第二步:shiro.ini配置文件
創建一個shiro.ini配置,編寫權限認證信息。
注意事項:
- shiro.ini文件名可以任意編寫,但后綴必須是ini
- shiro.ini配置文件放在classpath根目錄下
shiro.ini規則說明
[main] #用於配置SecurityManager里面的對象 對象名=類全限制名 對象名.屬性[.屬性...] = 值
[users] #用於配置用戶名信息 用戶名= 密碼, 角色1, 角色2, …, 角色N
[roles] #用於配置角色信息 角色名= 權限1, 權限2, …, 權限N #全部權限使用 * (星號)
[urls] #用於配置路徑攔截規則 |
權限格式使用:權限:操作:操作
代碼如下:
[users] jim = jim,admin
[roles] admin = * |
2.3.3. 第三步:創建SecurityManager對象
創建一個SecurityManager對象,並且測試校驗是否成功
package cn.gzsxt.shiro;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject;
public class ShiroDemo {
public static void main(String[] args) { // 1.獲得SecurityManager對象 IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.createInstance();
//2.設置SecurityUtils使用的安全管理器是securityManager SecurityUtils.setSecurityManager(securityManager);
//2.獲得subject Subject subject = SecurityUtils.getSubject(); AuthenticationToken token=new UsernamePasswordToken("jim", "jim"); //3.校驗用戶名密碼是否正確
try { Subject resultSubject = securityManager.login(subject, token); //獲得用戶名 System.out.println(resultSubject.getPrincipal()); //判斷是否擁有admin角色 boolean hasRole = resultSubject.hasRole("admin"); System.out.println(hasRole); } catch (AuthenticationException e) { System.out.println("校驗失敗,用戶名或者密碼不正確"); e.printStackTrace(); } } } |
2.3.4. 注意事項
以上示例,我們需要記住並且理解入門示例中的幾個API的使用
IniSecurityManagerFactory:作用加載ini配置文件獲得SecurityManagerFactory對象
SecurityManager:安全管理容器,就是否則整個Shiro框架授權校驗對象的管理
SecurityUtils :SecurityManager對象幫助類
Subject:驗證通過后用於存儲授權信息的身份對象
UsernamePasswordToken :用於設置校驗信息的對象
IncorrectCredentialsException :密碼出錯異常
UnknownAccountException:用戶名出錯異常
3. Realm的使用
3.1. Realm機制的必要性
以上案例,我們看到,我們的用戶驗證信息來自於ini配置文件的[users]以及[roles]標記。這樣的難以符合我們實際的需求。
我們希望可以將Shiro校驗的用戶信息存儲在數據庫里面,在從數據庫里面讀取出來。
解決方案:Shiro是通過Realm機制,實現將配置文件的校驗用戶信息存放在數據庫、LDAP等數據存儲系統里面。
說白了,現在我們要做的事情,就是從數據庫里面獲得用戶的驗證信息!!!
3.2. 訪問流程圖說明
|
如圖所示:
- 我們需通過Subject封裝訪問用戶的信息
- 我們需要一個SecurityManager對象來管理所有用戶的權限
- 我們需要ini配置文件配置獲得Realm對象
- 我們需要在Realm進行權限驗證以及授權
3.3. 配置步驟說明
第一步:導入包
第二步:創建shiro.ini配置文件
第三步:創建入口的測試類對象
第四步:創建Realm對象
第五步:配置shiro.ini調用Realm對象
3.4. 權限校驗-配置入門
3.4.1. 第一步:導入包
|
3.4.2. 第二步:創建shiro.ini配置文件
[main] #聲明realm對象 myRealm=cn.gzsxt.realm.MyRealm
#指定securityManager的realm對象 securityManager.realms=$myRealm |
3.4.3. 第三步:編寫一個測試類
package cn.gzsxt.test;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.junit.Test;
public class PermissionTest {
@Test public void authc(){
try { //第一步:獲得SecurityManager對象 IniSecurityManagerFactory ismf=new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = ismf.createInstance();
//第二步:構造一個subject SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
//第三步:封裝用戶名密碼(身份信息) UsernamePasswordToken token=new UsernamePasswordToken("zhangsan", "123456");
//第四步:校驗(登錄) Subject resultSubject = securityManager.login(subject, token);
//第五步:驗證是否通過 System.out.println(resultSubject.isAuthenticated()); } catch (AuthenticationException e) { e.printStackTrace(); }
} } |
3.4.4. 第四步:編寫Realm
package cn.gzsxt.realm;
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.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection;
/** * 自動一個MyRealm類,用於權限驗證以及權限授予 * * @author ranger * */ public class MyRealm extends AuthorizingRealm {
/** * 權限驗證 所謂的權限驗證,就是驗證訪問者(subject).是否使用有使用權限的身份。 說白了,就驗證用戶名密碼是否正確 * * 如果校驗成功,我們就返回AuthenticationInfo對象 如果校驗失敗,我們需要返回一個異常 * UnknownAccountException * */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("用戶名:" + token.getPrincipal()); if (token.getPrincipal().equals("zhangsan")) { return new SimpleAuthenticationInfo(token.getPrincipal(), "123456", this.getName());
} else { return null; } }
/** * 授權 根據通過校驗的身份(subject),說白了就是登錄成功的訪問者,我們給予什么權限。 * 將查詢到的權限信息封裝在AuthorizationInfo里面返回!! */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection collection) { return null; }
}
|
3.4.5. 加密
需求:我們的密碼是明文的,我們需要將密碼Md5加密。
問題:我們發現我們自己寫的Md5的類,無法傳遞給SimpleAuthenticationInfo對象,作為密碼校驗。如何解決的這個問題呢?
答:shiro框架自帶的密碼加密的功能。
- SimpleHash類:用於生成指定的Hash算法。
|
- HashedCredentialsMatcher類:用於讓Realm校驗時,校驗指定的Hash算法
|
- ByteSource 用於給Hash算法加鹽的
|
--創建Md5密碼
package cn.gzsxt.test;
import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.util.ByteSource;
//注意事項:設置的創建密碼的參數必須與校驗器的參數保持一致 public class CreatePasswordUtils {
public static void main(String[] args) { //加密方式 String hashAlgorithmName = "MD5"; //明文密碼 Object credentials = "123456"; //鹽值 Object salt = ByteSource.Util.bytes("nchu234we"); //迭代加密的次數, int hashIterations = 1; //返回加密的結果 Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); //加密后的密碼 System.out.println(result); }
}
|
--修改配置文件
[main] # 對象名 =類全限制名 ,就是創建一個對象 myRealm = cn.gzsxt.realm.MyRealm
#加密的對象 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher #設置加密的算法為md5 ,屬性對應的是set方法 credentialsMatcher.hashAlgorithmName=md5 #設置md5加密次數為1次 credentialsMatcher.hashIterations=1 #設置md5算法是加鹽 credentialsMatcher.hashSalted=true
#將加密對象指定給對應給myRealm對象 myRealm.credentialsMatcher=$credentialsMatcher
#將realm對象加入到securityManager容器里面 #引用 securityManager.realms = $對象名 #securityManager對象名是shiro固定的。用於指定securityManager容器 #對象的引用格式為: 對象名.屬性名 = $對象名; securityManager.realms = $myRealm |
--修改校驗代碼
package cn.gzsxt.realm;
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.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource;
/** * Realm :所有Realm的父接口 * AuthenticatingRealm :只有權限校驗,沒有權限授權的Realm * AuthorizingRealm : 既有權限校驗,權限授權的Realm (主要就是使用它) * JdbcRealm:使用實現的對數據庫 操作的代碼的Realm。它已經寫死了表名,必要要按它定義的表名定義數據庫表。(靈活性差) * * 注意事項:使用Realm,將驗證認證信息與獲得授權信息,都放在一起!!!!! * @author ranger * * */ public class MyRealm extends AuthorizingRealm {
/** * 用於權限校驗 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { try { System.out.println("=權限校驗=");
//第一步:獲得請求過來的用戶名 Object principal = token.getPrincipal();
//第二步:根據用戶查詢數據庫,返回用戶信息 (模擬數據,查詢到了"admin") if("admin".equals(principal)){ //第三步:如果用戶名相同,校驗密碼,如果密碼正確,不會報異常,如果校驗不通過就報異常 //參數1:用戶名 //參數2:校驗的密碼,從數據庫查詢出來的 //參數3:realm的名字 。隨便設置一個唯一字符串
//4). md5加密鹽值 ByteSource salt = ByteSource.Util.bytes("nchu234we");
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(principal,"8763d15228c560fed665e1fe73b2f601",salt,this.getName()); return authenticationInfo; } } catch (Exception e) { e.printStackTrace(); } return null; }
/** * 權限授權 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection token) { return null; } } |
3.4.6. 返回的認證信息為一個實體(JavaBean、Map)
注意:現在校驗后返回的認證信息是一個字符串的用戶名,而我們如果將Shiro的校驗功能用到登錄的邏輯里面,我明顯需要返回的不是一個用戶名,而是用戶的信息。
用戶的信息,我們只能使用一個實體類來封裝。可以是JavaBean或者是Map
答:我們校驗方法返回的SimpleAuthenticationInfo的構建方法的第一個參數就是用於指定,返回的用戶認證信息的。可以將用戶名修改為一個我們指定的實體類對象就可以了!!!
--構建一個實體類
package cn.gzsxt.javabean;
import java.io.Serializable;
/** * 注意:因為Shiro是一個支持緩存的框架,所以實體類需要實現序列化接口 * @author ranger * */ public class ActiveUser implements Serializable{
private static final long serialVersionUID = -1354674546192347496L; private Integer userId; public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } private String username; private String password; private Integer age; private Integer status; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public static long getSerialversionuid() { return serialVersionUID; }
}
|
--修改返回的認證信息
package cn.gzsxt.realm;
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.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource;
import cn.gzsxt.javabean.ActiveUser;
/** * Realm :所有Realm的父接口 * AuthenticatingRealm :只有權限校驗,沒有權限授權的Realm * AuthorizingRealm : 既有權限校驗,權限授權的Realm (主要就是使用它) * JdbcRealm:使用實現的對數據庫 操作的代碼的Realm。它已經寫死了表名,必要要按它定義的表名定義數據庫表。(靈活性差) * * 注意事項:使用Realm,將驗證認證信息與獲得授權信息,都放在一起!!!!! * @author ranger * * */ public class MyRealm extends AuthorizingRealm {
/** * 用於權限校驗 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { try { System.out.println("=權限校驗=");
//第一步:獲得請求過來的用戶名 Object principal = token.getPrincipal();
//第二步:根據用戶查詢數據庫,返回用戶信息 (模擬數據,查詢到了"admin") if("admin".equals(principal)){ //第三步:如果用戶名相同,校驗密碼,如果密碼正確,不會報異常,如果校驗不通過就報異常 //參數1:用戶名 //參數2:校驗的密碼,從數據庫查詢出來的 //參數3:realm的名字 。隨便設置一個唯一字符串
ActiveUser user=new ActiveUser(); user.setUserId(1); user.setAge(20); user.setPassword("8763d15228c560fed665e1fe73b2f601"); user.setUsername("admin"); user.setStatus(0);
//4). md5加密鹽值 ByteSource credentialsSalt = ByteSource.Util.bytes("nchu234we"); //參數1:用於設置認證信息,返回給調用對象的 //參數2:校驗的密碼 //參數3:如果配置了Md5加密,而且設置了需要加鹽,該參數就是密碼的鹽 //參數4:指定realm的名字,隨便寫個唯一的字符串就可以。建議直接使用this.getName(); SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user,"8763d15228c560fed665e1fe73b2f601",credentialsSalt,this.getName()); return authenticationInfo; } } catch (Exception e) { e.printStackTrace(); } //如果校驗成功,返回null return null; }
/** * 權限授權 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection token) { return null; } } |
3.5. 權限授予-配置入門
3.5.1. 第一步:檢驗Subject對象的權限校驗方法
--校驗代碼
package cn.gzsxt.test;
import java.util.ArrayList; import java.util.List;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject;
/** * 注意:根據示例 * 1.身份信息是放在Subject這個身份對象里面的 * 2.Shiro的所有對象是通過securityManager來管理 * 3.可以在配置文件配置,認證的信息(數據庫里面的表數據) * @author ranger * */ public class ShiroTest {
public static void main(String[] args) { //第一步:讀取配置文件 IniSecurityManagerFactory factory= new IniSecurityManagerFactory("classpath:shiro.ini"); //第二步:獲得安全管理器 SecurityManager securityManager = factory.createInstance(); //第三步:通過安全管理器幫助類構建一個Subject(身份) SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject();
//第四步:構建身份信息(用戶名/密碼) UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");
//第五步:校驗是否成功,將一個空的身份,以及身份信息通過登錄方法,如果成功返回帶身份信息的Subject對象。如果不成功就報異常 try { Subject result = securityManager.login(subject, token); System.out.println("認證成功,用戶名"+result.getPrincipals());
boolean flag = result.isAuthenticated(); //使用校驗方法,校驗用戶對應的角色 boolean hasRole = result.hasRole("roleAdmin"); System.out.println("是否有指定的角色:"+hasRole); List<String> hasRoles=new ArrayList<>(); hasRoles.add("roleAdmin"); hasRoles.add("roleEdu"); boolean allRoles = result.hasAllRoles(hasRoles); System.out.println("是否都包括集合的所有角色:"+allRoles);
//校驗用戶是否有對應的權限 boolean permitted = result.isPermitted("user:add"); System.out.println("是否有user:add權限:"+permitted);
boolean permitted2 = result.isPermitted("modular:add"); System.out.println("是否有modular:add權限:"+permitted2);
System.out.println(flag); } catch (AuthenticationException e) { System.out.println("用戶名或者密碼出錯"); e.printStackTrace(); } } } |
---配置文件
#users標簽:用於指定用戶信息,以及用戶的角色 #注意:shiro的支持一個用於有多個角色的 #用戶名= 密碼, 角色1, 角色2, …, 角色N #如果出現多個角色,用於取的是角色的並集 [users] admin = 123456,roleAdmin,roleEdu #roles標簽:用於設置角色的信息,包括的角色以及權限 #權限字符串的格式為:模塊名:操作:操作... 類似我們的子權限的設置 [roles] roleAdmin = user:query,user:add,user:delete,modular:* roleEdu = edu:query,edu:add |
3.5.2. 第二步:在Realm授權
根據以上代碼,我們確認,在配置文件配置的角色以及權限只可以通過權限校驗的。
問題:我們以后開發程序是不可以將程序的權限以及角色寫在配置文件里面的。而是寫在數據庫里面!!!我們如何將數據庫里面的權限設置給我們對象的認證者呢?
答:在我們AuthorizingRealm提供了權限授予的方法doGetAuthorizationInfo可以用於授權。替換在配置文件的權限、角色信息。
--JavaBean
package cn.gzsxt.javabean;
import java.io.Serializable;
/** * 注意:因為Shiro是一個支持緩存的框架,所以實體類需要實現序列化接口 * @author ranger * */ public class ActiveUser implements Serializable{
private static final long serialVersionUID = -1354674546192347496L; private Integer userId; public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } private String username; private String password; private Integer age; private Integer status;
public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public static long getSerialversionuid() { return serialVersionUID; }
}
|
--Realm
package cn.gzsxt.realm;
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.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource;
import cn.gzsxt.javabean.ActiveUser; import cn.gzsxt.javabean.Role;
/** * Realm :所有Realm的父接口 * AuthenticatingRealm :只有權限校驗,沒有權限授權的Realm * AuthorizingRealm : 既有權限校驗,權限授權的Realm (主要就是使用它) * JdbcRealm:使用實現的對數據庫 操作的代碼的Realm。它已經寫死了表名,必要要按它定義的表名定義數據庫表。(靈活性差) * * 注意事項:使用Realm,將驗證認證信息與獲得授權信息,都放在一起!!!!! * @author ranger * * */ public class MyRealm extends AuthorizingRealm {
/** * 用於權限校驗 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { try { System.out.println("=權限校驗=");
//第一步:獲得請求過來的用戶名 Object principal = token.getPrincipal();
//第二步:根據用戶查詢數據庫,返回用戶信息 (模擬數據,查詢到了"admin") if("admin".equals(principal)){ //第三步:如果用戶名相同,校驗密碼,如果密碼正確,不會報異常,如果校驗不通過就報異常 //參數1:用戶名 //參數2:校驗的密碼,從數據庫查詢出來的 //參數3:realm的名字 。隨便設置一個唯一字符串
ActiveUser user=new ActiveUser(); user.setUserId(1); user.setAge(20); user.setPassword("8763d15228c560fed665e1fe73b2f601"); user.setUsername("admin"); user.setStatus(0);
//4). md5加密鹽值 ByteSource credentialsSalt = ByteSource.Util.bytes("nchu234we"); //參數1:用於設置認證信息,返回給調用對象的 //參數2:校驗的密碼 //參數3:如果配置了Md5加密,而且設置了需要加鹽,該參數就是密碼的鹽 //參數4:指定realm的名字,隨便寫個唯一的字符串就可以。建議直接使用this.getName(); SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user,"8763d15228c560fed665e1fe73b2f601",credentialsSalt,this.getName()); return authenticationInfo; } } catch (Exception e) { e.printStackTrace(); }
return null; }
/** * 權限授權,必須在通過驗證后才執行的方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection token) {
System.out.println("===權限授予===="); //獲得當前通過驗證的用戶,為什么可以獲得校驗后的用戶信息呢? //答:因為就是如果通不過校驗,肯定就會有有授權,進入了授權肯定就有校驗的認證用戶了
ActiveUser user=(ActiveUser) token.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
authorizationInfo.addStringPermission("user:query"); authorizationInfo.addStringPermission("user:add"); authorizationInfo.addStringPermission("user:delete"); authorizationInfo.addStringPermission("user:edit"); authorizationInfo.addStringPermission("modular:*");
authorizationInfo.addRole("roleAdmin");
//將權限設置角色里面 Role role=new Role(); role.setPermisssion(authorizationInfo.getStringPermissions()); user.setRole(role);
return authorizationInfo; } } |
--配置文件
[main] # 對象名 =類全限制名 ,就是創建一個對象 myRealm = cn.gzsxt.realm.MyRealm
#加密的對象 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher #設置加密的算法為md5 credentialsMatcher.hashAlgorithmName=md5 #設置md5加密次數為1次 credentialsMatcher.hashIterations=1 #設置md5算法是加鹽 credentialsMatcher.hashSalted=true
#將加密對象指定給對應給myRealm對象 myRealm.credentialsMatcher=$credentialsMatcher
#將realm對象加入到securityManager容器里面 #引用 securityManager.realms = $對象名 #securityManager對象名是shiro固定的。用於指定securityManager容器 #對象的引用格式為: 對象名.屬性名 = $對象名; securityManager.realms = $myRealm |
--測試代碼
package cn.gzsxt.test;
import java.util.ArrayList; import java.util.List;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject;
import cn.gzsxt.javabean.ActiveUser; import cn.gzsxt.javabean.Role;
/** * 注意:根據示例 * 1.身份信息是放在Subject這個身份對象里面的 * 2.Shiro的所有對象是通過securityManager來管理 * 3.可以在配置文件配置,認證的信息(數據庫里面的表數據) * @author ranger * */ public class ShiroTest {
public static void main(String[] args) { //第一步:讀取配置文件 IniSecurityManagerFactory factory= new IniSecurityManagerFactory("classpath:shiro.ini"); //第二步:獲得安全管理器 SecurityManager securityManager = factory.createInstance(); //第三步:通過安全管理器幫助類構建一個Subject(身份) SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject();
//第四步:構建身份信息(用戶名/密碼) UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");
//第五步:校驗是否成功,將一個空的身份,以及身份信息通過登錄方法,如果成功返回帶身份信息的Subject對象。如果不成功就報異常 try { Subject result = securityManager.login(subject, token); System.out.println("認證成功,用戶名"+result.getPrincipals());
boolean flag = result.isAuthenticated(); //使用校驗方法,校驗用戶對應的角色 boolean hasRole = result.hasRole("roleAdmin"); System.out.println("是否有指定的角色:"+hasRole); List<String> hasRoles=new ArrayList<>(); hasRoles.add("roleAdmin"); hasRoles.add("roleEdu"); boolean allRoles = result.hasAllRoles(hasRoles); System.out.println("是否都包括集合的所有角色:"+allRoles);
//校驗用戶是否有對應的權限 boolean permitted = result.isPermitted("user:add"); System.out.println("是否有user:add權限:"+permitted);
boolean permitted2 = result.isPermitted("modular:add"); System.out.println("是否有modular:add權限:"+permitted2);
System.out.println(flag);
} catch (AuthenticationException e) { System.out.println("用戶名或者密碼出錯"); e.printStackTrace(); } } } |
4. 綜合案例
在數據庫里面設計,權限信息表。然后通過shiro讀取數據庫的信息。實現驗證與授權。
4.1. 第一步:數據庫設計
-- 導出 shiro-1115 的數據庫結構 DROP DATABASE IF EXISTS `shiro-1115`; CREATE DATABASE IF NOT EXISTS `shiro-1115` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `shiro-1115`;
-- 導出 表 shiro-1115.tb_admin 結構 DROP TABLE IF EXISTS `tb_admin`; CREATE TABLE IF NOT EXISTS `tb_admin` ( `admin_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '管理員編號', `admin_name` varchar(50) DEFAULT NULL COMMENT '管理員名', `admin_account` varchar(50) DEFAULT NULL COMMENT '登錄賬號', `admin_pwd` varchar(50) DEFAULT NULL COMMENT '登錄密碼', `create_date` datetime DEFAULT NULL COMMENT '創建日期', `admin_status` int(11) DEFAULT NULL COMMENT '管理員狀態 0可用,1禁用', `role_id` bigint(20) DEFAULT NULL COMMENT '角色編號', PRIMARY KEY (`admin_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='管理員';
-- 正在導出表 shiro-1115.tb_admin 的數據:~1 rows (大約) DELETE FROM `tb_admin`; /*!40000 ALTER TABLE `tb_admin` DISABLE KEYS */; INSERT INTO `tb_admin` (`admin_id`, `admin_name`, `admin_account`, `admin_pwd`, `create_date`, `admin_status`, `role_id`) VALUES (1, '配置管理員', 'admin', '123456', '2019-04-02 16:33:39', 0, 1); /*!40000 ALTER TABLE `tb_admin` ENABLE KEYS */;
-- 導出 表 shiro-1115.tb_dictionary 結構 DROP TABLE IF EXISTS `tb_dictionary`; CREATE TABLE IF NOT EXISTS `tb_dictionary` ( `dictionary_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典編號', `dictionary_name` varchar(200) DEFAULT NULL COMMENT '字典名', `dictionary_value` varchar(500) DEFAULT NULL COMMENT '字典值', `dictionary_type_code` bigint(20) DEFAULT NULL COMMENT '字段類型編碼', `dictionary_type_name` varchar(200) DEFAULT NULL COMMENT '字段類型名稱', PRIMARY KEY (`dictionary_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='tb_dictionary';
-- 正在導出表 shiro-1115.tb_dictionary 的數據:~0 rows (大約) DELETE FROM `tb_dictionary`; /*!40000 ALTER TABLE `tb_dictionary` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_dictionary` ENABLE KEYS */;
-- 導出 表 shiro-1115.tb_modular 結構 DROP TABLE IF EXISTS `tb_modular`; CREATE TABLE IF NOT EXISTS `tb_modular` ( `modular_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '模塊編號', `modular_name` varchar(200) DEFAULT NULL COMMENT '模塊名', `modular_sort` bigint(20) DEFAULT NULL COMMENT '排序順序', PRIMARY KEY (`modular_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='模塊表';
-- 正在導出表 shiro-1115.tb_modular 的數據:~0 rows (大約) DELETE FROM `tb_modular`; /*!40000 ALTER TABLE `tb_modular` DISABLE KEYS */; INSERT INTO `tb_modular` (`modular_id`, `modular_name`, `modular_sort`) VALUES (1, '系統管理模塊', 0); /*!40000 ALTER TABLE `tb_modular` ENABLE KEYS */;
-- 導出 表 shiro-1115.tb_permission 結構 DROP TABLE IF EXISTS `tb_permission`; CREATE TABLE IF NOT EXISTS `tb_permission` ( `permission_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '權限編號', `permission_name` varchar(200) DEFAULT NULL COMMENT '權限名', `permission_action` varchar(500) DEFAULT NULL COMMENT '權限路徑,匹配路徑是否有權限', `permission_is_show` int(11) DEFAULT NULL COMMENT '是否顯示', `permission_key` varchar(500) DEFAULT NULL COMMENT '父菜單編號,0表示頂級菜單', `modular_id` bigint(20) DEFAULT NULL COMMENT '模塊編號', PRIMARY KEY (`permission_id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='權限表';
-- 正在導出表 shiro-1115.tb_permission 的數據:~2 rows (大約) DELETE FROM `tb_permission`; /*!40000 ALTER TABLE `tb_permission` DISABLE KEYS */; INSERT INTO `tb_permission` (`permission_id`, `permission_name`, `permission_action`, `permission_is_show`, `permission_key`, `modular_id`) VALUES (1, '管理員管理', '/admin/toAdminList', 0, 'admin:list', 1), (2, '管理員管理-To增加', '/admin/toAdminAdd', 0, 'admin:list:to_add', 1), (3, '管理員管理-增加', '/admin/addAdmin', 0, 'admin:list:add', 1); /*!40000 ALTER TABLE `tb_permission` ENABLE KEYS */;
-- 導出 表 shiro-1115.tb_role 結構 DROP TABLE IF EXISTS `tb_role`; CREATE TABLE IF NOT EXISTS `tb_role` ( `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色編號', `role_name` varchar(50) DEFAULT NULL COMMENT '角色名', `role_permissions` varchar(1000) DEFAULT NULL COMMENT '權限集,權限編號使用逗號分隔', PRIMARY KEY (`role_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色';
-- 正在導出表 shiro-1115.tb_role 的數據:~1 rows (大約) DELETE FROM `tb_role`; /*!40000 ALTER TABLE `tb_role` DISABLE KEYS */; INSERT INTO `tb_role` (`role_id`, `role_name`, `role_permissions`) VALUES (1, '超級管理員', '1,2,3'); /*!40000 ALTER TABLE `tb_role` ENABLE KEYS */; |
4.2. 第二步:配置Shiro入門示例
--配置文件
[users] admin = 123456,roleAdmin [roles] roleAdmin = user:list,user:list:add,user:list:to_add |
--測試入口
package cn.gzsxt.test;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject;
public class ShiroTest {
public static void main(String[] args) { //第一步:讀取配置文件,創建SecurityManager容器 IniSecurityManagerFactory factory=new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.createInstance();
//第二步:通過SecurityUtil獲得一個身份對象 SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject();
//第三步:構建一個身份信息(Token) UsernamePasswordToken token=new UsernamePasswordToken("admin","123456"); //驗證信息,獲得配置文件的身份信息以及權限
Subject resultSubject = securityManager.login(subject, token);
System.out.println("用戶信息:"+resultSubject.getPrincipal()); }
} |
4.3. 第三步:編寫實體類
--Admin
package cn.gzsxt.pojo;
import java.io.Serializable;
public class Admin implements Serializable {
private static final long serialVersionUID = -4165903044029644075L;
private Long adminId;//BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '管理員編號', private String adminName;//VARCHAR(50) NULL DEFAULT NULL COMMENT '管理員名', private String adminAccount;//VARCHAR(50) NULL DEFAULT NULL COMMENT '登錄賬號', private String adminPwd;//VARCHAR(50) NULL DEFAULT NULL COMMENT '登錄密碼', private String createDate;//DATETIME NULL DEFAULT NULL COMMENT '創建日期', private Integer adminStatus;//INT(11) NULL DEFAULT NULL COMMENT '管理員狀態 0可用,1禁用', private Long roleId;//BIGINT(20) NULL DEFAULT NULL COMMENT '角色編號',
//一個管理員只能是有一個角色 private Role role;
public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } public Long getAdminId() { return adminId; } public void setAdminId(Long adminId) { this.adminId = adminId; } public String getAdminName() { return adminName; } public void setAdminName(String adminName) { this.adminName = adminName; } public String getAdminAccount() { return adminAccount; } public void setAdminAccount(String adminAccount) { this.adminAccount = adminAccount; } public String getAdminPwd() { return adminPwd; } public void setAdminPwd(String adminPwd) { this.adminPwd = adminPwd; } public String getCreateDate() { return createDate; } public void setCreateDate(String createDate) { this.createDate = createDate; } public Integer getAdminStatus() { return adminStatus; } public void setAdminStatus(Integer adminStatus) { this.adminStatus = adminStatus; } public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } } |
--Role
package cn.gzsxt.pojo;
import java.io.Serializable; import java.util.List;
public class Role implements Serializable {
private static final long serialVersionUID = 7439278538619165462L;
private Long roleId;//BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '角色編號', private String roleName;//VARCHAR(50) NULL DEFAULT NULL COMMENT '角色名', private String rolePermissions;//VARCHAR(1000) NULL DEFAULT NULL COMMENT '權限集,權限編號使用逗號分隔', //一個就是有多個權限 private List<Permission> permissions; public List<Permission> getPermissions() { return permissions; } public void setPermissions(List<Permission> permissions) { this.permissions = permissions; } public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getRolePermissions() { return rolePermissions; } public void setRolePermissions(String rolePermissions) { this.rolePermissions = rolePermissions; }
} |
--Permission
package cn.gzsxt.pojo;
import java.io.Serializable;
public class Permission implements Serializable {
private static final long serialVersionUID = -8720560773258570437L;
private Long permissionId;//BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '權限編號', private String permissionName;//VARCHAR(200) NULL DEFAULT NULL COMMENT '權限名', private String permissionAction;//VARCHAR(500) NULL DEFAULT NULL COMMENT '權限路徑,匹配路徑是否有權限', private Integer permissionIsShow;//INT(11) NULL DEFAULT NULL COMMENT '是否顯示', private String permissionKey;//VARCHAR(500) NULL DEFAULT NULL COMMENT '父菜單編號,0表示頂級菜單', private Long modularId;//BIGINT(20) NULL DEFAULT NULL COMMENT '模塊編號', public Long getPermissionId() { return permissionId; } public void setPermissionId(Long permissionId) { this.permissionId = permissionId; } public String getPermissionName() { return permissionName; } public void setPermissionName(String permissionName) { this.permissionName = permissionName; } public String getPermissionAction() { return permissionAction; } public void setPermissionAction(String permissionAction) { this.permissionAction = permissionAction; } public Integer getPermissionIsShow() { return permissionIsShow; } public void setPermissionIsShow(Integer permissionIsShow) { this.permissionIsShow = permissionIsShow; } public String getPermissionKey() { return permissionKey; } public void setPermissionKey(String permissionKey) { this.permissionKey = permissionKey; } public Long getModularId() { return modularId; } public void setModularId(Long modularId) { this.modularId = modularId; } } |
4.4. 第四步:編寫數據庫連接
package cn.gzsxt.utils;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException;
public class DbUitls {
//1.獲得數據庫連接 public static Connection getConnection(){ //四要素 String driver="org.gjt.mm.mysql.Driver"; String url="jdbc:mysql://localhost:3306/shiro-1115"; String user="root"; String password="123456";
//加載驅動 try { Class.forName(driver); return DriverManager.getConnection(url, user, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return null; }
public static void main(String[] args) { System.out.println(DbUitls.getConnection()); }
} |
4.5. 第五步:實現權限驗證
--權限驗證代碼
package cn.gzsxt.realm;
import java.sql.SQLException; import java.util.List;
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.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource;
import cn.gzsxt.dao.AdminDAO; import cn.gzsxt.dao.PermissionDAO; import cn.gzsxt.dao.RoleDAO; import cn.gzsxt.pojo.Admin; import cn.gzsxt.pojo.Permission; import cn.gzsxt.pojo.Role;
public class MyRealm extends AuthorizingRealm {
private AdminDAO adminDAO = new AdminDAO(); private RoleDAO roleDAO=new RoleDAO(); private PermissionDAO permissionDAO =new PermissionDAO();
/** * 權限驗證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("=權限驗證==");
// 連接數據庫,通過用戶名查詢用戶信息 // 1.獲得認證信息,賬號名 String adminAccount = (String) token.getPrincipal(); // 2.通過賬號名查詢查詢指定的管理員記錄 try { Admin admin = adminDAO.findByAccount(adminAccount); //密碼加鹽 ByteSource salt=ByteSource.Util.bytes("asdfwera"+admin.getAdminSalt()); SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(admin,admin.getAdminPwd(),salt,this.getName()); return authenticationInfo; } catch (SQLException e) { e.printStackTrace(); }
return null; }
/** * 權限授權,授予必須依賴權限驗證通過 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
}
}
|
--配置文件修改
#[users] # admin = 123456,roleAdmin #[roles] # roleAdmin = user:list,user:list:add,user:list:to_add
[main] #自定義Realm myRealm = cn.gzsxt.realm.MyRealm #創建一個Hash算法的對象 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher #設置hash算法是Md5算法 credentialsMatcher.hashAlgorithmName=md5 #設置迭代加密的次數 credentialsMatcher.hashIterations=2 #設置是否加鹽 credentialsMatcher.hashSalted=true
#指定自定義Realm支持hash算法加密的密碼 myRealm.credentialsMatcher=$credentialsMatcher securityManager.realms=$myRealm |
4.6. 第六步:實現權限授予
package cn.gzsxt.realm;
import java.sql.SQLException; import java.util.List;
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.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource;
import cn.gzsxt.dao.AdminDAO; import cn.gzsxt.dao.PermissionDAO; import cn.gzsxt.dao.RoleDAO; import cn.gzsxt.pojo.Admin; import cn.gzsxt.pojo.Permission; import cn.gzsxt.pojo.Role;
public class MyRealm extends AuthorizingRealm {
private AdminDAO adminDAO = new AdminDAO(); private RoleDAO roleDAO=new RoleDAO(); private PermissionDAO permissionDAO =new PermissionDAO();
/** * 權限驗證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("=權限驗證==");
// 連接數據庫,通過用戶名查詢用戶信息 // 1.獲得認證信息,賬號名 String adminAccount = (String) token.getPrincipal(); // 2.通過賬號名查詢查詢指定的管理員記錄 try { Admin admin = adminDAO.findByAccount(adminAccount); //密碼加鹽 ByteSource salt=ByteSource.Util.bytes("asdfwera"+admin.getAdminSalt()); SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(admin,admin.getAdminPwd(),salt,this.getName()); return authenticationInfo; } catch (SQLException e) { e.printStackTrace(); }
return null; }
/** * 權限授權,授予必須依賴權限驗證通過 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //獲得當前驗證通過的管理員 Admin admin=(Admin) principalCollection.getPrimaryPrincipal(); //通過當前管理員獲得角色編號 Long roleId = admin.getRoleId(); try { //通過角色編號查詢唯一的角色(單角色權限驗證) Role role = roleDAO.findByRoleId(roleId); //將角色信息放在管理員的實體類里面 admin.setRole(role);
//通過就是的權限ID集,獲得角色的權限 String rolePermissions = role.getRolePermissions(); List<Permission> permissions = permissionDAO.findByIds(rolePermissions); //將角色的權限賦予角色對象 role.setPermissions(permissions);
//創建一個權限授予對象 SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo(); //將角色設置給該對象 authorizationInfo.addRole(role.getRoleName()); //將權限設置給Shiro的權限對象 for (Permission permission : permissions) { authorizationInfo.addStringPermission(permission.getPermissionKey()); } //返回 return authorizationInfo;
} catch (SQLException e) { e.printStackTrace(); }
return null; }
}
|
5. 總結
- Shiro是什么
一個權限控制框架。
- Shiro的作用是什么
就是在實現Rbac系統的時候,使用它來做權限驗證,可以減少我們的開發的代碼。
- 我們將使用的API記住
IniSecurityManagerFactory : 用於加載配置文件,創建SecurityManager對象
SecurityManager :就是整個Shiro的控制對象
SecurityUtils :SecurityManager 工具類,用於獲得Subject對象
Subject :身份類,存儲返回的數據信息、提供了校驗的權限的方法
UsernamePasswordToken 身份信息構建類 (Token 令牌,作用就是傳入校驗參數)
AuthorizingRealm 支持校驗與授權的Realm
AuthenticationInfo 校驗成功返回的信息的父接口
SimpleAuthenticationInfo 校驗成功返回信息類
Md5Hash Md5加密類
ByteSource 字節碼處理工具類,我們在構造Md5加鹽時使用到。
HashedCredentialsMatcher Md5算法校驗器,用於支持Md5校驗
AuthorizationInfo 授權成功返回的信息類的父接口
PrincipalCollection 授予是獲得驗證信息的類
SimpleAuthorizationInfo 授權成功返回的信息類的實現類
|
6. Shiro整合SpringMVC
6.1. 說明
上面的知識,我們已經了解Shiro的權限授權、權限校驗、Md5的加密密碼的配置。
但是,我們的需求將Shiro框架用到Web項目。
所以,我們需要使用Shiro整合SpringMVC使用!!!
6.2. 整合步驟
6.2.1. 第一部分:SpringMVC框架的配置
6.2.1.1. 步驟說明
- 導入依賴的Jar包
- 構建一個請求頁面
- 創建業務控制器
- 配置web.xml的核心控制器
- 創建配置文件
- 構建一個返回頁面
6.2.1.2. 第一步:導入依賴的Jar包
|
6.2.1.3. 第二步:構建一個請求頁面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <a href="${pageContext.request.contextPath }/admin/addAdmin">addAdmin</a> </body> </html> |
6.2.1.4. 第三步:創建業務控制器
package cn.gzsxt.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.annotation.SessionScope;
@Controller @SessionScope @RequestMapping(value="admin") public class AdminController {
@RequestMapping(value="/addAdmin") public String addAdmin(){ System.out.println("=增加管理員="); return "/index.jsp"; }
}
|
6.2.1.5. 第四步:配置web.xml的核心控制器
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <display-name>shiro-demo-08-springmvc</display-name>
<!-- 配置核心控制器。攔截器所有的請求 --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定配置文件的路徑 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-*.xml</param-value> </init-param> <!-- 配置啟動的時候就創建對象 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> |
6.2.1.6. 第五步:創建配置文件
--SpringMVC配置文件,spring-mvc.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: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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 啟動默認注解支持 --> <mvc:annotation-driven /> <!-- 放開靜態資源訪問 --> <mvc:default-servlet-handler/>
</beans>
|
--Spring容器配置文件,spring-context.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" 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-4.3.xsd">
<!-- 掃描組件配置 --> <context:component-scan base-package="cn.gzsxt" /> </beans>
|
6.2.2. 第二部分:Shiro框架的配置
6.2.2.1. 配置步驟說明
- 導入依賴的Jar包
- 創建Shiro.ini配置文件
- 創建一個入口測試類
- 創建一個Realm
6.2.2.2. 第一步:導入依賴的Jar包
|
6.2.2.3. 第二步:創建Shiro.ini配置文件
[users] admin = 123456,adminRole [roles] adminRole = admin:list,admin:add,admin:delete,admin:edit |
6.2.2.4. 第三步:創建一個入口測試類
package cn.gzsxt.shiro.test;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject;
public class ShiroTest {
public static void main(String[] args) { //第一步:加載ini配置文件,獲得SecurityManagerFactory對象 IniSecurityManagerFactory factory=new IniSecurityManagerFactory("classpath:shiro.ini"); //第二步:獲得SecurityManager對象 SecurityManager securityManager = factory.createInstance(); //第三步:獲得Subject身份對象 SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject();
//第四步:構建一個身份信息對象(Token) UsernamePasswordToken token=new UsernamePasswordToken("admin","123456");
//第五步:login操作。校驗權限 Subject resultSubject = securityManager.login(subject, token);
//第六步:校驗是否成功 boolean isAuth= resultSubject.isAuthenticated(); System.out.println(isAuth);
}
}
|
6.2.2.5. 第四步:創建一個Realm
--修改配置文件
#[users] # admin = 123456,adminRole #[roles] # adminRole = admin:list,admin:add,admin:delete,admin:edit
[main] myRealm = cn.gzsxt.realm.MyRealm securityManager.realms=$myRealm |
--MyRealm類
package cn.gzsxt.realm;
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.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection;
public class MyRealm extends AuthorizingRealm {
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("=權限校驗="); try { if (token.getPrincipal().equals("admin")) { SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(), "123456", this.getName()); return info; } } catch (Exception e) { e.printStackTrace(); }
return null; }
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); info.addRole("role_admin"); info.addStringPermission("user:add"); info.addStringPermission("user:list"); //info.addStringPermission("user:edit");
return info;
}
}
|
6.2.2.6. 第五步:測試結果
--運行ShiroTest測試類,結果為:
|
6.2.3. 第三部分:SpringMVC整合Shiro
6.2.3.1. 說明
- 權限控制是對誰的控制?,就是對url訪問權限的控制。
- Filter的優先級別高於Servlet。而Shiro是使用Filter攔截的,SpringMVC是使用Servlet攔截請求的。那么我們如何讓Shiro交給SpringMVC代理呢?
答:Spring提供了一個Filter代理類,可以讓Spring容器代理Filter的操作,DelegatingFilterProxy。實現了在過濾里面可以調用Spring容器的對象,把我們原來配置在web.xml的過濾器配置在Spring配置文件里面。
6.2.3.2. Shiro提供的過濾器說明
在org.apache.shiro.web.filter.mgt.DefaultFilter枚舉里面定義可以使用的過濾器以及對於的枚舉值
|
anon : 匿名過濾器,該過濾器的作用就是用於配置不需要授權就可以直接訪問的路徑。
authc: 校驗過濾器,該過濾器的作用就是用於必須經過校驗才可以訪問的路徑(url)
logout: 注銷過濾器, 該過濾器的作用就是用於注銷退出登錄。
perms:權限驗證過濾器,用於權限驗證的
6.2.3.3. 配置步驟說明
- 在web.xml配置過濾器代理類DelegatingFilterProxy,讓Filter交個Spring容器代理
- 創建一個spring-shiro.xml配置文件
6.2.3.4. 第一步:配置web.xml
<filter> <filter-name>securityFilterBean</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 將生命周期交給Spring代理 --> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>securityFilterBean</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
6.2.3.5. 第二步:創建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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置攔截器調用的對象 注意事項:bean的名字必須要和web.xml的DelegatingFilterProxy的過濾的name屬性一一對應。 --> <bean name="securityFilterBean" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 指定跳轉到校驗不成功的路徑 ,不指定會跳轉上一次訪問菜單頁面 --> <!-- <property name="unauthorizedUrl" value="/undo"></property> --> <!-- 指定依賴的securityManager對象 --> <property name="securityManager" ref="securityManager"></property> <!-- 指定 登錄請求的路徑 --> <property name="loginUrl" value="/admin/loginAdmin"></property> <!-- 登錄成功跳轉的路徑 --> <property name="successUrl" value="/index"></property> <!-- 用於指定執行的過濾器鏈 ,配置多個過濾器連接對的url --> <property name="filterChainDefinitions"> <value> <!-- 指定url與過濾器的關系 --> <!-- 所有的路徑都要攔截 --> /admin/toLogin = anon /** = authc </value> </property>
</bean>
<!-- 2.指定securityManager的對象 --> <bean name="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm" />
</bean>
<!-- 3.創建一個Realm對象 --> <bean name="myRealm" class="cn.gzsxt.realm.MyRealm"></bean>
<!-- 4.Spring容器對shiro生命周期的管理 ,基於注解權限攔截需要配置--> <bean name="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
</beans> |
6.2.3.6. 第三步:權限控制器標簽的使用
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> 主頁 <a href="${pageContext.request.contextPath }/logout">退出</a><br/> <shiro:hasPermission name="user:list"> <!-- 如果有user:list權限才顯示 用戶列表 --> <a href="#"> 用戶列表</a><br/> </shiro:hasPermission> <shiro:hasPermission name="user:add"> <!-- 如果有user:add權限才顯示 用戶增加 --> <a href="#"> 用戶增加</a><br/> </shiro:hasPermission> <shiro:hasPermission name="user:edit"> <!-- 如果有user:edit權限才顯示 用戶編輯 --> <a href="#"> 用戶編輯</a><br/> </shiro:hasPermission> <shiro:hasPermission name="user:delete"> <!-- 如果有user:delete權限才顯示 用戶刪除 --> <a href="#"> 用戶刪除</a><br/> </shiro:hasPermission> </body> </html> |
7. Shiro整合SpringMVC基於注解
7.0.1. 配置流程說明
7.0.2. 第一步:配置web.xml
<filter> <filter-name>securityFilterBean</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 將生命周期交給Spring代理 --> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>securityFilterBean</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
7.0.3. 第二步:配置Shiro配置類
package cn.gzsxt.config;
import java.util.LinkedHashMap; import java.util.Map;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
import cn.gzsxt.realm.ShiroRealm;
@Configuration public class ShiroConfig {
//1.配置securityFilterBean對象 @Bean(name="securityFilter") public Object getShiroFilterFactory() { ShiroFilterFactoryBean factory=new ShiroFilterFactoryBean();
//設置屬性 factory.setLoginUrl("/login"); factory.setSuccessUrl("/index"); factory.setSecurityManager(this.getSecurityManager()); //基於配置的權限過濾器,順序執行,所以使用LinkedHashMap Map<String, String> filterChainDefinition=new LinkedHashMap<>(); filterChainDefinition.put("/**", "authc"); factory.setFilterChainDefinitionMap(filterChainDefinition);
Object securityFilter=null; try { securityFilter = factory.getObject(); } catch (Exception e) { e.printStackTrace(); } return securityFilter; }
//2.獲得securityManager對象 @Bean(name="securityManager") public DefaultWebSecurityManager getSecurityManager() { DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager(); //設置需要的realm對象 securityManager.setRealm(this.getRealm()); return securityManager; }
//3.配置realm對象 @Bean public ShiroRealm getRealm(){ ShiroRealm realm=new ShiroRealm(); return realm; } } |