基本使用
什么是權(quán)限管理?
基本上涉及到用戶參與的系統(tǒng)都要進(jìn)行權(quán)限管理,權(quán)限管理屬于系統(tǒng)安全的范疇,權(quán)限管理能實現(xiàn)對用戶訪問系統(tǒng)的控制个绍,按照安全規(guī)則或者安全策略限制用戶操作,只允許用戶訪問被授權(quán)的資源浪汪。權(quán)限管理包括用戶身份認(rèn)證和授權(quán)兩部分,簡稱認(rèn)證授權(quán)
身份認(rèn)證
就是判斷一個用戶是否為合法用戶的處理過程.醉常用的簡單身份認(rèn)證方式是系統(tǒng)通過核對用戶輸入的用戶名和口令,看其是否與系統(tǒng)中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確.對于采用指紋等系統(tǒng),則出示指紋凛虽;對于硬件Key等刷卡系統(tǒng),則需要刷卡.
上邊的流程圖中需 要理解以下關(guān)鍵對象:
Subject:主體 user
訪問系統(tǒng)的用戶死遭,主體可以是用戶、程序等凯旋,進(jìn)行認(rèn)證的都稱為主體呀潭;
Principal:身份信息(username)
是主體(subject)進(jìn)行身份認(rèn)證的標(biāo)識,標(biāo)識必須具有唯一性至非,如用戶名钠署、手機(jī)號、郵箱地址等荒椭,一個主體可以有多個身份谐鼎,但是必須有一個主身份(Primary Principal)。
credential:憑證信息(password)
是只有主體自己知道的安全信息趣惠,如密碼狸棍、證書等。授權(quán)味悄,即訪問控制草戈,控制誰能訪問哪些資源。主體進(jìn)行身份認(rèn)證后需要分配權(quán)限方可訪問系統(tǒng)的資源侍瑟,對于某些資源沒有權(quán)限是無法訪問的唐片。
Apache Shiro是Java的一個安全框架。幫助我們完成:認(rèn)證涨颜、授權(quán)费韭、加密、會話管理咐低、與Web集成揽思、緩存等。
Authentication:身份認(rèn)證/登錄见擦,驗證用戶是不是擁有相應(yīng)的身份钉汗;
Authorization:授權(quán)羹令,即權(quán)限驗證,驗證某個已認(rèn)證的用戶是否擁有某個權(quán)限损痰;即判斷用戶是否能做事情福侈,常見的如:驗證某個用戶是否擁有某個角色÷矗或者細(xì)粒度的驗證某個用戶對某個資源是否具有某個權(quán)限肪凛;
Session Manager:會話管理,即用戶登錄后就是一次會話辽社,在沒有退出之前伟墙,它的所有信息都在會話中;會話可以是普通JavaSE環(huán)境的滴铅,也可以是Web環(huán)境的戳葵;
Cryptography:加密,保護(hù)數(shù)據(jù)的安全性汉匙,如密碼加密存儲到數(shù)據(jù)庫拱烁,而不是明文存儲;
Web Support:Web支持噩翠,可以非常容易的集成到Web環(huán)境戏自;
Caching:緩存,比如用戶登錄后伤锚,其用戶信息擅笔、擁有的角色/權(quán)限不必每次去查,這樣可以提高效率见芹;
Concurrency:shiro支持多線程應(yīng)用的并發(fā)驗證剂娄,即如在一個線程中開啟另一個線程,能把權(quán)限自動傳播過去玄呛;
Testing:提供測試支持阅懦;
Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進(jìn)行訪問;
Remember Me:記住我徘铝,這個是非常常見的功能耳胎,即一次登錄后,下次再來的話不用登錄了惕它。
Shiro架構(gòu)有三個主要概念 - Subject怕午,SecurityManager,Realms
Subject : 訪問系統(tǒng)的用戶淹魄,主體可以是用戶郁惜、程序等,進(jìn)行認(rèn)證的都稱為主體甲锡;
Subject 一詞是一個安全術(shù)語兆蕉,其基本意思是“當(dāng)前的操作用戶”羽戒。它是一個抽象的概念,可以是人虎韵,也可以是第三方進(jìn)程或其他類似事物易稠,如爬蟲,機(jī)器人等包蓝。
在程序任意位置:Subject currentUser = SecurityUtils.getSubject(); 獲取shiro 一旦獲得Subject驶社,你就可以立即獲得你希望用Shiro為當(dāng)前用戶做的90%的事情,如登錄测萎、登出亡电、訪問會話、執(zhí)行授權(quán)檢查等
SecurityManager
安全管理器硅瞧,它是shiro功能實現(xiàn)的核心逊抡,負(fù)責(zé)與后邊介紹的其他組件(認(rèn)證器/授權(quán)器/緩存控制器)進(jìn)行交互,實現(xiàn)subject委托的各種功能零酪。有點類似于spirngmvc中的DispatcherServlet前端控制器。
Realms
Realm充當(dāng)了Shiro與應(yīng)用安全數(shù)據(jù)間的“橋梁”或者“連接器”拇勃。四苇;可以把Realm看成DataSource,即安全數(shù)據(jù)源方咆。執(zhí)行認(rèn)證(登錄)和授權(quán)(訪問控制)時月腋,Shiro會從應(yīng)用配置的Realm中查找相關(guān)的比對數(shù)據(jù)。以確認(rèn)用戶是否合法瓣赂,操作是否合理
從系統(tǒng)結(jié)構(gòu)角度看:shiro
Subject:主體榆骚,可以看到主體可以是任何可以與應(yīng)用交互的“用戶”;
SecurityManager:相當(dāng)于SpringMVC中的DispatcherServlet或者Struts2中的
FilterDispatcher煌集;是Shiro的心臟妓肢;所有具體的交互都通過SecurityManager進(jìn)行控制;它管理著所有Subject苫纤、且負(fù)責(zé)進(jìn)行認(rèn)證和授權(quán)碉钠、及會話、緩存的管理卷拘。
Authenticator:認(rèn)證器喊废,負(fù)責(zé)主體認(rèn)證的,這是一個擴(kuò)展點栗弟,如果用戶覺得Shiro默認(rèn)的不好污筷,可以自定義實現(xiàn);其需要認(rèn)證策略(Authentication Strategy)乍赫,即什么情況下算用戶認(rèn)證通過了瓣蛀;
Authorizer:授權(quán)器陆蟆,或者訪問控制器,用來決定主體是否有權(quán)限進(jìn)行相應(yīng)的操作揪惦;即控制著用戶能訪問應(yīng)用中的哪些功能遍搞;
Realm:可以有1個或多個Realm,可以認(rèn)為是安全實體數(shù)據(jù)源器腋,即用于獲取安全實體的溪猿;可以是JDBC實現(xiàn),也可以是LDAP實現(xiàn)纫塌,或者內(nèi)存實現(xiàn)等等诊县;由用戶提供;注意:Shiro不知道你的用戶/權(quán)限存儲在哪及以何種格式存儲措左;所以我們一般在應(yīng)用中都需要實現(xiàn)自己的Realm依痊;
SessionManager:如果寫過Servlet就應(yīng)該知道Session的概念,Session呢需要有人去管理它的生命周期怎披,這個組件就是SessionManager胸嘁;而Shiro并不僅僅可以用在Web環(huán)境,也可以用在如普通的JavaSE環(huán)境凉逛、EJB等環(huán)境性宏;所有呢,Shiro就抽象了一個自己的Session來管理主體與應(yīng)用之間交互的數(shù)據(jù)状飞;可以實現(xiàn)分布式的會話管理毫胜;
SessionDAO:DAO大家都用過,數(shù)據(jù)訪問對象诬辈,用于會話的CRUD酵使,比如我們想把Session保存到數(shù)據(jù)庫,那么可以實現(xiàn)自己的SessionDAO焙糟,通過如JDBC寫到數(shù)據(jù)庫口渔;比如想把Session放到redis中,可以實現(xiàn)自己的redis SessionDAO穿撮;另外SessionDAO中可以使用Cache進(jìn)行緩存搓劫,以提高性能;
CacheManager:緩存控制器混巧,來管理如用戶枪向、角色、權(quán)限等的緩存的咧党;因為這些數(shù)據(jù)基本上很少去改變秘蛔,放到緩存中后可以提高訪問的性能
Cryptography:密碼模塊,Shiro提高了一些常見的加密組件用于如密碼加密/解密的。
入門案例
創(chuàng)建 maven 項目 添加依賴
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
添加shiro所必須的配置文件信息
log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=TRACE
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro.ini
[users]
#模擬數(shù)據(jù)庫用戶
zhangsan=666
hello world 代碼
package cn.icanci.shiro.helloworld;
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;
import org.junit.Test;
import sun.security.smartcardio.SunPCSC;
/**
* @Author: icanci
* @ProjectName: shiro
* @PackageName: cn.icanci.shiro.helloworld
* @Date: Created in 2020/2/25 11:13
* @ClassAction: 測試 Shiro 驗證
*/
public class TestShiro {
@Test
public void testLogin() throws Exception {
//1.創(chuàng)建 SecurityManager工廠對象:加載配置文件 創(chuàng)建工廠對象
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.通過工廠對象 創(chuàng)建SecurityManager對象
SecurityManager instance = factory.getInstance();
//3.將SecurityManager綁定到當(dāng)選運(yùn)行環(huán)境中,目的使系統(tǒng)隨時隨地都可以訪問
SecurityUtils.setSecurityManager(instance);
//4.創(chuàng)建當(dāng)前登陸的主體 注意 此時主題沒有經(jīng)過認(rèn)證
Subject subject = SecurityUtils.getSubject();
//5.綁定主題所需的身份 參數(shù)1 用戶名 參數(shù)2 密碼
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "6626");
//6.主體登陸
subject.login(usernamePasswordToken);
//7.判斷身份登陸成功
System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());
//7.判斷身份注銷成功
subject.logout();
System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());
//沒有賬戶異常 UnknownAccountException
//賬戶正確 密碼錯誤異常 IncorrectCredentialsException
}
}
shiro登錄登出流程分析
1深员、調(diào)用subject.login方法進(jìn)行登錄负蠕,其會自動委托給securityManager.login方法進(jìn)行登錄;
2倦畅、securityManager通過Authenticator(認(rèn)證器)進(jìn)行認(rèn)證;
3遮糖、Authenticator的實現(xiàn)ModularRealmAuthenticator調(diào)用realm從ini配置文件取用戶真實的賬號和密碼,這里使用的是IniRealm(shiro自帶,相當(dāng)于數(shù)據(jù)源)叠赐;
4欲账、IniRealm先根據(jù)token中的賬號去ini中找該賬號,如果找不到則給ModularRealmAuthenticator返回null芭概,如果找到則匹配密碼赛不,匹配密碼成功則認(rèn)證通過。
5罢洲、最后調(diào)用Subject.logout進(jìn)行退出操作踢故。
存在的問題 1、用戶名/密碼硬編碼在ini配置文件惹苗,以后需要改成如數(shù)據(jù)庫存儲殿较,且密碼需要加密存儲;
自定義realm
步驟:
1:自定義reaml桩蓉,繼承 AuthorizingRealm 重寫3個方法:getName doGetAuthorizationInfo doGetAuthenticationInfo
shiro-realm.ini 配置文件
#聲明一個realm
myRealm=cn.icanci.shiro.realm.MyRealm
#指定securityManager的realms實現(xiàn)
securityManager.realms=$myRealm
實例代碼
package cn.icanci.shiro.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;
/**
* @Author: icanci
* @ProjectName: shiro
* @PackageName: cn.icanci.shiro.realm
* @Date: Created in 2020/2/25 12:47
* @ClassAction:
*/
public class MyRealm extends AuthorizingRealm {
@Override
public String getName() {
return "myRealm";
}
/**
* 授權(quán)操作
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 認(rèn)證操作
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//authenticationToken表示登陸的時候 包裝的 UsernamePasswordToken
//通過用戶名查找用戶到數(shù)據(jù)庫中查找用戶信息,封裝成一個AuthenticationToken對象,方便認(rèn)證器進(jìn)行認(rèn)證
//獲取參數(shù) authenticationToken 中的用戶名
String username = (String) authenticationToken.getPrincipal();
//通過用戶名查詢數(shù)據(jù)庫 將用戶對象數(shù)據(jù)查詢返回:賬戶和密碼
//假設(shè)查詢數(shù)據(jù)庫返回的數(shù)據(jù)是:zhangsan 666
if(!"zhangsan".equals(username)){
return null;
}
String password = "666";
//info對象表示realm登陸比對信息:參數(shù)1 用戶信息 (真實登陸中式對象user對象) 參數(shù)2 密碼 參數(shù)3 當(dāng)然 realm名字
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
System.out.println(authenticationToken);
return info;
}
}
測試
@Test
public void testMyRealm() throws Exception {
//1.創(chuàng)建 SecurityManager工廠對象:加載配置文件 創(chuàng)建工廠對象
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
//2.通過工廠對象 創(chuàng)建SecurityManager對象
SecurityManager instance = factory.getInstance();
//3.將SecurityManager綁定到當(dāng)選運(yùn)行環(huán)境中,目的使系統(tǒng)隨時隨地都可以訪問
SecurityUtils.setSecurityManager(instance);
//4.創(chuàng)建當(dāng)前登陸的主體 注意 此時主題沒有經(jīng)過認(rèn)證
Subject subject = SecurityUtils.getSubject();
//5.綁定主題所需的身份 參數(shù)1 用戶名 參數(shù)2 密碼
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "666");
//6.主體登陸
subject.login(usernamePasswordToken);
//7.判斷身份登陸成功
System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());
//7.判斷身份注銷成功
subject.logout();
System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());
//沒有賬戶異常 UnknownAccountException
//賬戶正確 密碼錯誤異常 IncorrectCredentialsException
}
shiro加密操作
散列算法 一般用于生成數(shù)據(jù)的摘要信息斜脂,是一種不可逆的算法,一般適合存儲密碼之類的數(shù)據(jù)触机,常見的散列算法如MD5、SHA等玷或。一般進(jìn)行散列時最好提供一個salt(鹽)儡首,比如加密密碼“admin”,產(chǎn)生的散列值是“21232f297a57a5a743894a0e4a801fc3”偏友,可以到一些md5解密網(wǎng)站很容易的通過散列值得到密碼“admin”蔬胯,即如果直接對密碼進(jìn)行散列相對來說破解更容易,此時我們可以加一些只有系統(tǒng)知道的干擾數(shù)據(jù)位他,如用戶名和ID(即鹽)氛濒;這樣散列的對象是“密碼+用戶名+ID”,這樣生成的散列值相對來說更難破解鹅髓。
@Test
public void testMD5() {
//明文密碼
String password = "666";
//加密
Md5Hash md5Hash = new Md5Hash(password);
System.out.println(md5Hash);
//fae0b27c451c728867a567e8c1bb4e53
//加密 yan
md5Hash = new Md5Hash(password,"張三");
System.out.println(md5Hash);
//e0d3fb0671e4d6305c5e5ab24dd05e51
//加密 yan + 散列次數(shù)
md5Hash = new Md5Hash(password,"張三",3);
System.out.println(md5Hash);
//7658aa971a52a0b0b2b2d89c5e5a0a0b
}
配置文件 shiro-cryptography.ini
[main]
#定義憑證匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次數(shù)
credentialsMatcher.hashIterations=3
#將憑證匹配器設(shè)置到realm
myRealm=cn.icanci.shiro.helloworld.PasswordRealm
myRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$myRealm
鹽值加密
package cn.icanci.shiro.helloworld;
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;
/**
* @Author: icanci
* @ProjectName: shiro
* @PackageName: cn.icanci.shiro.helloworld
* @Date: Created in 2020/2/25 13:28
* @ClassAction: 加密PasswordRealm
*/
public class PasswordRealm extends AuthorizingRealm {
@Override
public String getName() {
return super.getName();
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//authenticationToken表示登陸的時候 包裝的 UsernamePasswordToken
//通過用戶名查找用戶到數(shù)據(jù)庫中查找用戶信息,封裝成一個AuthenticationToken對象,方便認(rèn)證器進(jìn)行認(rèn)證
//獲取參數(shù) authenticationToken 中的用戶名
String username = (String) authenticationToken.getPrincipal();
//通過用戶名查詢數(shù)據(jù)庫 將用戶對象數(shù)據(jù)查詢返回:賬戶和密碼
//假設(shè)查詢數(shù)據(jù)庫返回的數(shù)據(jù)是:zhangsan 666
if(!"zhangsan".equals(username)){
return null;
}
String password = "666";
//模擬數(shù)據(jù)庫保存得加密之后的密碼 賬號 zhangsan : 666+張三+散列次數(shù) 3
password = "7658aa971a52a0b0b2b2d89c5e5a0a0b";
//info對象表示realm登陸比對信息:參數(shù)1 用戶信息 (真實登陸中式對象user對象) 參數(shù)2 密碼 參數(shù)3 鹽 參數(shù)4 當(dāng)然 realm名字
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes("張三"), getName());
System.out.println(authenticationToken);
return info;
}
}
測試
@Test
public void testPassword() throws Exception {
//1.創(chuàng)建 SecurityManager工廠對象:加載配置文件 創(chuàng)建工廠對象
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-cryptography.ini");
//2.通過工廠對象 創(chuàng)建SecurityManager對象
SecurityManager instance = factory.getInstance();
//3.將SecurityManager綁定到當(dāng)選運(yùn)行環(huán)境中,目的使系統(tǒng)隨時隨地都可以訪問
SecurityUtils.setSecurityManager(instance);
//4.創(chuàng)建當(dāng)前登陸的主體 注意 此時主題沒有經(jīng)過認(rèn)證
Subject subject = SecurityUtils.getSubject();
//5.綁定主題所需的身份 參數(shù)1 用戶名 參數(shù)2 密碼
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "666");
//6.主體登陸
subject.login(usernamePasswordToken);
//7.判斷身份登陸成功
System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());
//7.判斷身份注銷成功
subject.logout();
System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());
//沒有賬戶異常 UnknownAccountException
//賬戶正確 密碼錯誤異常 IncorrectCredentialsException
}
授權(quán)方式
RBAC: 基于角色的權(quán)限管理
簡單理解為:誰扮演什么角色舞竿, 被允許做什么操作
用戶對象:user: 當(dāng)前操作用戶
角色對象:role:表示權(quán)限操作許可權(quán)的集合
權(quán)限對象:permission: 資源操作許可權(quán)
例子:張三(user) 下載(permission)一個高清無碼的種子(資源), 需要VIP權(quán)限(role)
張三--->普通用戶--->授權(quán)---->VIP用戶----->下載種子
編程方式:
通過寫if/else授權(quán)代碼塊完成
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有權(quán)限
} else {
//無權(quán)限
}
注解方式:
通過在執(zhí)行的Java方法上放置相應(yīng)的注解完成
@RequiresRoles("admin")
@RequiresPermission(“employee:save”)
public void hello() {
//有權(quán)限
}
jsp標(biāo)簽方式:
在JSP頁面通過相應(yīng)的標(biāo)簽完成
<shiro:hasRole name="admin">
<!— 有權(quán)限 —>
</shiro:hasRole>
使用 ini 授權(quán)方式
增加 shiro-permission.ini 配置文件
[users]
#用戶zhang的密碼是123窿冯,此用戶具有role1和role2兩個角色
zhangsan=666,role1,role2
lisi=888,role2
[roles]
#角色role1對資源user擁有create骗奖、update權(quán)限
role1=user:create,user:update
#角色role2對資源user擁有create、delete權(quán)限
role2=user:create,user:delete
#角色role3對資源user擁有create權(quán)限
role3=user:create
權(quán)限表達(dá)式定義
在ini文件中用戶、角色执桌、權(quán)限的配置規(guī)則是:“用戶名=密碼鄙皇,角色1,角色2...” “角色=權(quán)限1仰挣,權(quán)限2...”伴逸,首先根據(jù)用戶名找角色,再根據(jù)角色找權(quán)限膘壶,角色是權(quán)限集合错蝴。
權(quán)限字符串的規(guī)則是:“資源標(biāo)識符:操作:資源實例標(biāo)識符”,意思是對哪個資源的哪個實例具有什么操作香椎,“:”是資源/操作/實例的分割符漱竖,權(quán)限字符串也可以使用*通配符。
例子:
用戶創(chuàng)建權(quán)限:user:create畜伐,或user:create:*
用戶修改實例001的權(quán)限:user:update:001
用戶實例001的所有權(quán)限:user:*:001一般的馍惹,我們操作只需要關(guān)注前面兩節(jié):
資源:操作 :
*:* : 所有資源的所有操作權(quán)限--->admin
角色測試
@Test
public void testHasRole() throws Exception {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");
SecurityManager instance = factory.getInstance();
SecurityUtils.setSecurityManager(instance);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "666");
subject.login(usernamePasswordToken);
//進(jìn)行授權(quán)操作之前,用戶必須通過認(rèn)真
//判斷當(dāng)前用戶是否有某個角色 返回true表示擁有
boolean role1 = subject.hasRole("role1");
System.out.println(role1);
//判斷當(dāng)前用戶是否擁有這些權(quán)限 返回true表示全部擁有 false 表示不全部擁有
boolean b = subject.hasAllRoles(Arrays.asList("role1", "role2", "role3"));
System.out.println(b);
//判斷當(dāng)前用戶是否擁有這些權(quán)限 返回 boolean數(shù)組 返回true表示擁有 false 表示不擁有
boolean[] booleans = subject.hasRoles(Arrays.asList("role1", "role2", "role3"));
for (int i = 0; i < booleans.length; i++){
System.out.println(booleans[i]);
}
//判斷是否有角色 沒有返回值 如果有角色 不做任何操作 如果沒有 拋出異常 UnauthorizedException
subject.checkRole("role1");
//判斷是否擁有一些角色 不是全部擁有就 UnauthorizedException
subject.checkRoles("role1","role2","role3");
}
自定義realm完成授權(quán)
使用之前的配置文件
@Test
public void testHasRoleSecurity() throws Exception {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");
SecurityManager instance = factory.getInstance();
SecurityUtils.setSecurityManager(instance);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "666");
subject.login(usernamePasswordToken);
//進(jìn)行授操作之前,用戶必須通過認(rèn)證
//判斷是否有某個權(quán)限 返 true 擁有 false 沒有
boolean permitted = subject.isPermitted("user:delete");
System.out.println(permitted);
//判斷是否有某一些權(quán)限 返 true 都擁有 false 不都擁有
boolean permittedAll = subject.isPermittedAll("user:create", "user:delete");
System.out.println(permittedAll);
//判斷當(dāng)前用戶是否擁有一些權(quán)限 有就返回 true 沒有就返回 false
boolean[] permitted1 = subject.isPermitted("user:create", "user:delete");
System.out.println("-----------------------");
for (int i = 0; i < permitted1.length; i++){
System.out.println(permitted1[i]);
}
//判斷用戶是否擁有 有就沒有返回值 沒有就報異常 UnauthorizedException
subject.checkPermission("user:delete");
subject.checkPermission("user:updateAll");
}
步驟:1:自定義PermissionRealm 繼承 AuthorizingRealm 重寫3個方法: getName doGetAuthorizationInfo doGetAuthenticationInfo
配置文件 shiro-permission-realm.ini
[main]
#聲明一個realm
myReal=cn.icanci.shiro.realm.PermissionRealm
#指定securityManager的realms實現(xiàn)
securityManager.realms=$myReal
package cn.icanci.shiro.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 java.util.ArrayList;
import java.util.List;
/**
* @Author: icanci
* @ProjectName: shiro
* @PackageName: cn.icanci.shiro.realm
* @Date: Created in 2020/2/25 12:47
* @ClassAction:
*/
public class PermissionRealm extends AuthorizingRealm {
@Override
public String getName() {
return "PermissionRealm";
}
/**
* 授權(quán)操作
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//闖入?yún)?shù) principalCollection 憑證信息
//SimpleAuthenticationInfo 認(rèn)證方法返回封裝認(rèn)證信息種第一個參數(shù):用戶信息(username)
//當(dāng)前登陸的用戶名信息
String username = (String) principalCollection.getPrimaryPrincipal();
//模擬查詢數(shù)據(jù)庫 查詢用戶的角色以及用戶權(quán)限
//角色集合
List<String> roles = new ArrayList<>();
//權(quán)限集合
List<String> permission = new ArrayList<>();
//假設(shè)數(shù)據(jù)庫種 有role角色
roles.add("role1");
//假設(shè)用戶在數(shù)據(jù)庫擁有刪除權(quán)限
permission.add("user:delete");
//返回當(dāng)前用戶在數(shù)據(jù)庫的權(quán)限和角色
SimpleAuthorizationInfo info= new SimpleAuthorizationInfo();
info.addRoles(roles);
info.addStringPermissions(permission);
return info;
}
/**
* 認(rèn)證操作
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//authenticationToken表示登陸的時候 包裝的 UsernamePasswordToken
//通過用戶名查找用戶到數(shù)據(jù)庫中查找用戶信息,封裝成一個AuthenticationToken對象,方便認(rèn)證器進(jìn)行認(rèn)證
//獲取參數(shù) authenticationToken 中的用戶名
String username = (String) authenticationToken.getPrincipal();
//通過用戶名查詢數(shù)據(jù)庫 將用戶對象數(shù)據(jù)查詢返回:賬戶和密碼
//假設(shè)查詢數(shù)據(jù)庫返回的數(shù)據(jù)是:zhangsan 666
if(!"zhangsan".equals(username)){
return null;
}
String password = "666";
//info對象表示realm登陸比對信息:參數(shù)1 用戶信息 (真實登陸中式對象user對象) 參數(shù)2 密碼 參數(shù)3 當(dāng)然 realm名字
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
System.out.println(authenticationToken);
return info;
}
}
測試類
@Test
public void testRoleSecurity() throws Exception {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-permission-realm.ini");
SecurityManager instance = factory.getInstance();
SecurityUtils.setSecurityManager(instance);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "666");
subject.login(usernamePasswordToken);
//進(jìn)行授操作之前,用戶必須通過認(rèn)證
//判斷是否有某個權(quán)限 返 true 擁有 false 沒有
boolean permitted = subject.isPermitted("user:delete");
System.out.println(permitted);
//判斷當(dāng)前用戶是否有某個角色 返回true表示擁有
boolean role1 = subject.hasRole("role1");
System.out.println(role1);
}
授權(quán)流程分析
1、首先調(diào)用Subject.isPermitted/hasRole接口玛界,其會委托給SecurityManager万矾,而SecurityManager接著會委托給Authorizer
2、Authorizer是真正的授權(quán)者慎框,如果我們調(diào)用如isPermitted(“user:view”)良狈,其首先會通過PermissionResolver把字符串轉(zhuǎn)換成相應(yīng)的Permission實例;
3笨枯、在進(jìn)行授權(quán)之前薪丁,其會調(diào)用相應(yīng)的Realm獲取Subject相應(yīng)的角色/權(quán)限用于匹配傳入的角色/權(quán)限;
4馅精、Authorizer會判斷Realm的角色/權(quán)限是否和傳入的匹配严嗜,如果有多個Realm,會委托給ModularRealmAuthorizer進(jìn)行循環(huán)判斷洲敢,如果匹配如isPermitted/hasRole會返回true漫玄,否則返回false表示授權(quán)失敗。
授權(quán)源碼分析
1压彭、首先調(diào)用Subject.isPermitted/hasRole接口睦优,其會委托給SecurityManager,而SecurityManager接著會委托給Authorizer
2壮不、Authorizer是真正的授權(quán)者汗盘,如果我們調(diào)用如isPermitted(“user:view”),其首先會通過PermissionResolver把字符串轉(zhuǎn)換成相應(yīng)的Permission實例询一;
3衡未、在進(jìn)行授權(quán)之前尸执,其會調(diào)用相應(yīng)的Realm獲取Subject相應(yīng)的角色/權(quán)限用于匹配傳入的角色/權(quán)限;
4缓醋、Authorizer會判斷Realm的角色/權(quán)限是否和傳入的匹配如失,如果有多個Realm,會委托給ModularRealmAuthorizer進(jìn)行循環(huán)判斷送粱,如果匹配如isPermitted/hasRole會返回true褪贵,否則返回false表示授權(quán)失敗。