??Java中權(quán)限控制框架-Shiro
??推薦網(wǎng)站(不斷完善中):個(gè)人博客
??個(gè)人主頁:個(gè)人主頁
??相關(guān)專欄:CSDN專欄
??立志賺錢,干活想躺,瞎分享的摸魚工程師一枚
??前言
在我們實(shí)戰(zhàn)開發(fā)過程中,對于權(quán)限的控制是必不可少的,一個(gè)系統(tǒng)中常見的有
普通會(huì)員、管理員、超級管理員
等等不同的角色出現(xiàn)。我們?nèi)绾胃鼉?yōu)雅的在Java中使用權(quán)限框架?來看看Java中比較火熱的權(quán)限框架之一
shiro
吧~
1.簡介
1.1.什么是Shiro
? Shiro是apache旗下一個(gè)開源框架,它將軟件系統(tǒng)的安全認(rèn)證相關(guān)的功能抽取出來,實(shí)現(xiàn)用戶身份認(rèn)證,權(quán)限授權(quán)、加密、會(huì)話管理等功能,組成了一個(gè)通用的安全認(rèn)證框架。
? 使用shiro可以非??焖俚耐瓿?mark>認(rèn)證、授權(quán)等功能的開發(fā),降低系統(tǒng)成本。
官方地址
1.2.Shiro功能簡介
shiro中的三大核心組建:Subject、SecurityManager、Realm
- Subject:代表了當(dāng)前用戶的安全操作(也就是該接口中定義了許多與授權(quán)認(rèn)證相關(guān)的方法)
- SecurityManager:它是Shiro框架的核心,Shiro通過SecurityManager來管理內(nèi)部組件實(shí)例,并通過它來提供安全管理的各種服務(wù)。
- Realm:Realm充當(dāng)了Shiro與應(yīng)用安全數(shù)據(jù)間的“橋梁”或者“連接器”。也就是說,當(dāng)對用戶執(zhí)行認(rèn)證(登錄)和授權(quán)(訪問控制)驗(yàn)證時(shí),Shiro會(huì)從應(yīng)用配置的Realm中查找用戶及其權(quán)限信息
其他常見功能組件
Authentication:身份認(rèn)證/登錄,驗(yàn)證用戶是不是擁有相應(yīng)的身份
Authorization:授權(quán),即權(quán)限驗(yàn)證,驗(yàn)證某個(gè)已認(rèn)證的用戶是否擁有某個(gè)權(quán)限;即判斷用 戶是否能進(jìn)行什么操作,如:驗(yàn)證某個(gè)用戶是否擁有某個(gè)角色。或者細(xì)粒度的驗(yàn)證某個(gè)用戶對某個(gè)資源是否具有某個(gè)權(quán)限;
SessionManager:SessionManager即會(huì)話管理,shiro框架定義了一套會(huì)話管理,它不依賴web容器的session,所以shiro可以使用在非web應(yīng)用上,也可以將分布式應(yīng)用的會(huì)話集中在一點(diǎn)管理,此特性可使它實(shí)現(xiàn)單點(diǎn)登錄。
SessionDao:SessionDAO即會(huì)話dao,是對session會(huì)話操作的一套接口,比如要將會(huì)話信息存儲(chǔ)到redis數(shù)據(jù)庫
CacheManager:CacheManager即緩存管理,將用戶權(quán)限數(shù)據(jù)存儲(chǔ)在緩存,這樣可以提高性能
Cryptography:即密碼管理,shiro提供了一套加密/解密的組件,方便開發(fā)。比如提供常用的散列、加/解密等功能
1.3.Shiro架構(gòu)圖
2.關(guān)于權(quán)限控制
2.1.權(quán)限邏輯
權(quán)限管理的范圍應(yīng)該是怎么樣的?
? 基本上涉及到用戶參與的系統(tǒng)都要進(jìn)行權(quán)限管理,權(quán)限管理屬于系統(tǒng)安全的范疇,權(quán)限管理實(shí)現(xiàn)對用戶訪問系統(tǒng)的控制,按照安全規(guī)則或者安全策略控制用戶可以訪問而且只能訪問自己被授權(quán)的資源
? 權(quán)限管理包括身份認(rèn)證和授權(quán)兩部分,簡稱認(rèn)證授權(quán)。對于需要訪問控制的資源用戶首先經(jīng)過身份認(rèn)證,認(rèn)證通過后用戶具有該資源的訪問權(quán)限方可訪問
關(guān)于認(rèn)證:身份認(rèn)證,就是判斷一個(gè)用戶是否為合法用戶的處理過程
權(quán)限認(rèn)證流程圖
2.2.權(quán)限模型
在權(quán)限中無非就這么幾個(gè)概念內(nèi)容為:主體、資源、權(quán)限(Permission)
- 主體:訪問資源者
- 資源:各種信息
- 權(quán)限:規(guī)定主體對資源的訪問權(quán)
模型圖
主體(賬號、密碼)、 資源(資源名稱、訪問地址)、權(quán)限(權(quán)限名稱、資源id)、 角色(角色名稱)
角色和權(quán)限關(guān)系(角色id、權(quán)限id)、 主體和角色關(guān)系(主體id、角色id)
實(shí)際開發(fā)常見模型圖
? 實(shí)際企業(yè)開發(fā)中會(huì)將資源與權(quán)限表合為一張表。
? 資源(資源名稱、訪問地址) 權(quán)限(權(quán)限名稱、資源id)合并為:資源(權(quán)限名稱、資源名稱、資源訪問地址)
2.2.1.權(quán)限解決方案
對資源類型的管理稱為粗顆粒度權(quán)限管理,即只控制到菜單、按鈕、方法等
? 例如:用戶具有用戶管理的權(quán)限,具有導(dǎo)出訂單明細(xì)的權(quán)限
對資源實(shí)例的控制稱為細(xì)顆粒度權(quán)限管理,即控制到數(shù)據(jù)級別的權(quán)限
? 例如:用戶只允許修改本部門的員工信息,用戶只允許導(dǎo)出自己創(chuàng)建的訂單明細(xì)
對于粗顆粒度的權(quán)限管理可以很容易做系統(tǒng)架構(gòu)級別的功能,即系統(tǒng)功能操作使用統(tǒng)一的粗顆粒度的權(quán)限管理。
對于細(xì)顆粒度的權(quán)限管理不建議做成系統(tǒng)架構(gòu)級別的功能,因?yàn)閷?shù)據(jù)級別的控制是系統(tǒng)的業(yè)務(wù)需求,隨著業(yè)務(wù)需求的變更業(yè)務(wù)功能變化的可能性很大,建議對數(shù)據(jù)級別的權(quán)限控制在業(yè)務(wù)層個(gè)性化開發(fā),
? 例如:用戶只允許修改自己創(chuàng)建的商品信息可以在service接口添加校驗(yàn)實(shí)現(xiàn),service接口需要傳入當(dāng)前操作人的標(biāo)識,與商品信息創(chuàng)建人標(biāo)識對比,不一致則不允許修改商品信息
2.3.URL權(quán)限攔截
基于url攔截是企業(yè)中常用的權(quán)限管理方法,實(shí)現(xiàn)思路是:將系統(tǒng)操作的每個(gè)url配置在權(quán)限表中,將權(quán)限對應(yīng)到角色,將角色分配給用戶,用戶訪問系統(tǒng)功能通過Filter進(jìn)行過慮,過慮器獲取到用戶訪問的url,只要訪問的url是用戶分配角色中的url則放行繼續(xù)訪問
3.簡單入門
3.1.環(huán)境搭建
pom.xml文件
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- junit start -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
日志配置
log4j.rootLogger=debug,console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%p] %c %m %n
shiro.ini配置
? shiro.ini是shiro默認(rèn)可以識別的配置文件,將與權(quán)限相關(guān)的內(nèi)容寫在shiro.ini文件中可以直接被加載
? 通過shiro.ini配置文件初始化SecurityManager環(huán)境
[users]
zhangsan=123456,admin
lisi=654321,superadmin
[roles]
admin=product:create,product:update,product:delete
superadmin=product:create,product:update,product:delete,user:delete
3.2.實(shí)戰(zhàn)
3.2.1.實(shí)戰(zhàn)流程
構(gòu)建一個(gè)SecurityManager環(huán)境的流程
- 初始化一個(gè)SecurityManager(安全管理器)對象
- 通過shiro.ini文件初始化一個(gè)SecurityManager工廠
- 通過工廠獲取SecurityManager(安全管理器)
- 使用SecurityUtils將securityManager設(shè)置到運(yùn)行環(huán)境中
- 創(chuàng)建一個(gè)Subject實(shí)例,securityManager會(huì)管理該主體
- 創(chuàng)建token令牌,記錄用戶認(rèn)證的身份和憑證即賬號和密碼
- 認(rèn)證
3.2.2.實(shí)戰(zhàn)測試
@Test
public void shiro(){
//通過shiro.ini初始化一個(gè)SecurityManager工廠
IniSecurityManagerFactory iniSecurityManagerFactory=new IniSecurityManagerFactory("classpath:shiro.ini");
//通過工廠獲取一個(gè)SecurityManager管理器
SecurityManager securityManager = iniSecurityManagerFactory.getInstance();
//使用SecurityUtils將securityManager設(shè)置到運(yùn)行環(huán)境中
SecurityUtils.setSecurityManager(securityManager);
//創(chuàng)建一個(gè)Subject實(shí)例
Subject subject = SecurityUtils.getSubject();
//創(chuàng)建token令牌,記錄用戶登陸的身份和憑證即賬號密碼
UsernamePasswordToken user = new UsernamePasswordToken("zhangsan", "123456");
//登陸認(rèn)證(如果認(rèn)證不通過回拋異常)
subject.login(user);
//查詢認(rèn)證狀態(tài)
System.out.println(subject.isAuthenticated());
//查詢用戶的授權(quán)信息
System.out.println("是否有刪除用戶的權(quán)限" + subject.isPermitted("user:delete"));
System.out.println("是否有添加產(chǎn)品的權(quán)限" + subject.isPermitted("product:create"));
//查詢用戶的授權(quán)角色信息
System.out.println("是否擁有admin的角色權(quán)限" + subject.hasRole("admin"));
System.out.println("是否擁有superadmin的角色權(quán)限" + subject.hasRole("superadmin"));
//退出登陸
subject.logout();
//退出登陸之后再查看權(quán)限
System.out.println(subject.isAuthenticated());
System.out.println("是否有刪除用戶的權(quán)限" + subject.isPermitted("user:delete"));
System.out.println("是否有添加產(chǎn)品的權(quán)限" + subject.isPermitted("product:create"));
}
3.2.3.常見異常
UnknownAccountException:賬號不存在
IncorrectCredentialsException:輸入密碼錯(cuò)誤
DisabledAccountException:賬號被禁用
LockedAccountException:登陸失敗次數(shù)過多
ExpiredCredentialsException:憑證過期
3.2.4.身份認(rèn)證流程
- 首先調(diào)用 Subject.login(token) 進(jìn)行登錄,其會(huì)自動(dòng)委托給SecurityManager
- SecurityManager 負(fù)責(zé)真正的身份驗(yàn)證邏輯;它會(huì)委托給Authenticator 進(jìn)行身份驗(yàn)證
- Authenticator 才是真正的身份驗(yàn)證者,Shiro API 中核心的身份認(rèn)證入口點(diǎn),此處可以自定義插入自己的實(shí)現(xiàn)
- Authenticator 可能會(huì)委托給相應(yīng)的 AuthenticationStrategy 進(jìn) 行多 Realm 身份驗(yàn)證,默認(rèn) ModularRealmAuthenticator 會(huì)調(diào)用AuthenticationStrategy 進(jìn)行多 Realm 身份驗(yàn)證
- Authenticator 會(huì)把相應(yīng)的 token 傳入 Realm,從 Realm 獲取身份驗(yàn)證信息,如果沒有返回/拋出異常表示身份驗(yàn)證失敗了。此處可以配置多個(gè)Realm,將按照相應(yīng)的順序及策略進(jìn)行訪問
3.3.自定義Realm
? 上邊的程序使用的是Shiro自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息,大部分情況下需要從系統(tǒng)的數(shù)據(jù)庫中讀取用戶信息,所以需要自定義realm,實(shí)現(xiàn)自定義Realm一般繼承 AuthorizingRealm(授權(quán))即可;其繼承了AuthenticatingRealm(即身份驗(yàn)證),而且也間接繼承了CachingRealm(帶有緩存實(shí)現(xiàn))
3.3.1.相關(guān)代碼
用戶
public class User {
//用戶名、密碼、用戶狀態(tài)
private String username;
private String password;
//0鎖定、1正常
private String status;
}
自定義Realm認(rèn)證部分代碼
/**
* 認(rèn)證
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//從令牌中獲取到用戶輸入的用戶名
String usernmae = (String) token.getPrincipal();
//獲取用戶輸入的密碼(因?yàn)楂@取的是二進(jìn)制需要轉(zhuǎn)換)
String password = new String ((char[]) token.getCredentials());
//模擬從數(shù)據(jù)庫中獲取對應(yīng)的用戶數(shù)據(jù)
User admin = new User("admin", "123456","1");
//進(jìn)行信息比對
if (!usernmae.equals(admin.getUsername())) {
throw new UnknownAccountException("用戶不存在");
}
if (!password.equals(admin.getPassword())){
throw new IncorrectCredentialsException("密碼錯(cuò)誤");
}
if ("0".equals(admin.getStatus())){
throw new LockedAccountException("賬戶被鎖定,請聯(lián)系管理員");
}
//如果沒有異常則表示認(rèn)證通過則返回一個(gè)簡單的認(rèn)證數(shù)據(jù)模型
return new SimpleAuthenticationInfo(usernmae, password, getName());
}
自定義Realm授權(quán)部分代碼
**
* 授權(quán)
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//初始化一個(gè)簡單授權(quán)對象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//給授權(quán)對象設(shè)置權(quán)限
//設(shè)置角色
Set<String> roles = new HashSet<>();
//模擬從數(shù)據(jù)庫的角色表中獲取角色添加信息
roles.add("admin");
roles.add("superadmin");
//設(shè)置權(quán)限
Set<String> permissions=new HashSet<>();
permissions.add("user:create");
permissions.add("user:delete");
permissions.add("user:update");
//添加到授權(quán)信息中
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
3.3.2.配置與測試
關(guān)于Shiro.ini配置
在shiro.ini配置中需要將內(nèi)容進(jìn)行更新,不再使用手動(dòng)進(jìn)行認(rèn)證,標(biāo)明自定義Realm的類路徑,使用自定義Realm。
#[users]
#zhangsan=123456,admin
#lisi=654321,superadmin
#
#[roles]
#admin=product:create,product:update,product:delete
#superadmin=product:create,product:update,product:delete
#自定義realm
shiroRealm=com.imcode.common.shiro.ShiroRelam
#將realm設(shè)置到securityManager
securityManager.realms=$shiroRealm
關(guān)于測試
@Test
public void demo02(){
//通過shiro.ini初始化一個(gè)SecurityManager工廠
IniSecurityManagerFactory iniSecurityManagerFactory=new IniSecurityManagerFactory("classpath:shiro.ini");
//通過工廠獲取一個(gè)SecurityManager管理器
SecurityManager securityManager = iniSecurityManagerFactory.getInstance();
//使用SecurityUtils將securityManager設(shè)置到運(yùn)行環(huán)境中
SecurityUtils.setSecurityManager(securityManager);
//創(chuàng)建一個(gè)Subject實(shí)例
Subject subject = SecurityUtils.getSubject();
//創(chuàng)建token令牌,記錄用戶登陸的身份和憑證即賬號密碼
UsernamePasswordToken user = new UsernamePasswordToken("admin", "123456");
//登陸
subject.login(user);
//測試
System.out.println(subject.isPermitted("user:create"));
System.out.println(subject.hasRole("admin"));
}
3.4.關(guān)于Permissions
Shiro將Permission定義為定義顯式行為或操作的語句。它是應(yīng)用程序中原始功能的聲明,僅此而已。權(quán)限是安全策略中最低級別的構(gòu)造,它們只顯式定義應(yīng)用程序可以執(zhí)行的操作。
3.4.1.簡單案例
假設(shè)您希望添加對公司打印機(jī)的訪問權(quán)限,以限制某些人可以打印到特定的打印機(jī),而其他人可以查詢當(dāng)前隊(duì)列。
一種非常簡單的方法是授予用戶“queryPrinter”權(quán)限。然后,您可以調(diào)用查看權(quán)限的方法來判斷用戶是否具有queryPrinter的權(quán)限:
subject.isPermitted("queryPrinter")
3.4.2.關(guān)于permissions規(guī)則
資源標(biāo)識符:操作:對象實(shí)例 ID 即對哪個(gè)資源的哪個(gè)實(shí)例可以進(jìn)行什么操作.
其默認(rèn)支持通配符權(quán)限字符串,:
表 示資源/操作/實(shí)例的分割;,
表示操作的分割,*
表示任意資源/操作/實(shí)例。
案例
例如:user:query、user:edit
– 冒號是一個(gè)特殊字符,它用來分隔權(quán)限字符串的下一部件:第一部分是權(quán)限被操作的領(lǐng)域,第二部分是被執(zhí)行的操作
。
多個(gè)值
每個(gè)部分都可以包含多個(gè)值。因此,您不必向用戶授予“printer:print”和“printer:query”權(quán)限,而只需授予他們一個(gè)權(quán)限:
printer:print,query
全部值
如果您想授予用戶特定部分中的所有值,該怎么辦?這樣做比手動(dòng)列出每個(gè)值更方便。同樣,基于通配符,我們可以做到這一點(diǎn)。如果printer域有3個(gè)可能的行動(dòng)(query,print,和manage),這一點(diǎn)
printer:query,print,manage
可以變成
printer:*
4.常用組件
4.1.關(guān)于加密
4.1.1.散列算法
散列算法一般用于生成一段文本的摘要信息,散列算法不可逆,將內(nèi)容可以生成摘要,無法將摘要轉(zhuǎn)成原始內(nèi)容。散列算法常用于對密碼進(jìn)行散列,常用的散列算法有MD5、SHA。
? 一般散列算法需要提供一個(gè)salt(鹽)與原始內(nèi)容生成摘要信息,這樣做的目的是為了安全性,比如:111111的md5值是:96e79218965eb72c92a549dd5a330112,拿著“96e79218965eb72c92a549dd5a330112”去md5破解網(wǎng)站很容易進(jìn)行破解,如果要是對111111和salt(鹽,一個(gè)隨機(jī)數(shù))進(jìn)行散列,這樣雖然密碼都是111111,但是加不同的鹽會(huì)生成不同的散列值。
案例代碼
@Test
public void test() {
//md5加密,不加鹽
String password_md5 = new Md5Hash("111111").toString();
System.out.println("password_md5=" + password_md5);
//md5加密,加鹽
String password_md5_salt_1 = new Md5Hash("111111", "imcode", 1).toString();
System.out.println("password_md5_salt_1=" + password_md5_salt_1);
//兩次散列相當(dāng)于md5(md5())
String password_md5_salt_2 = new Md5Hash("111111", "imcode", 2).toString();
System.out.println("password_md5_salt_2=" + password_md5_salt_2);
}
4.1.2.加密工具類
可以將shiro自帶的加密類寫成一個(gè)通用類。
public class MD5Util {
// 散列次數(shù)
private static int hashIterations = 3;
/**
* md5加密工具類
*/
public static String md5(String source, String salt) {
return new Md5Hash(source, salt, hashIterations).toString();
}
}
4.1.3.對密碼進(jìn)行加密
一般在數(shù)據(jù)庫中的密碼我們通常不會(huì)以明文的方式進(jìn)行顯示,會(huì)通過加密的方式加強(qiáng)密碼的安全度。
@Test
public void test02(){
//對密碼進(jìn)行加密
ShiroUtil.login("admin",MD5Util.md5("123456","ak47"));
Subject subject = SecurityUtils.getSubject();
System.out.println(subject.hasRole("admin"));
System.out.println(subject.isPermitted("user:create"));
}
4.2.關(guān)于緩存
利用緩存可以提高效率,比如用戶登錄后,其用戶信息、擁有的角色/權(quán)限不必每次去查,這樣可以提高效率。
4.2.1.場景
每次查詢用戶的權(quán)限的時(shí)候都會(huì)調(diào)用一次shiro的授權(quán)方法,使用緩存可以實(shí)現(xiàn)只有在用戶認(rèn)證成功的時(shí)候調(diào)用一下授權(quán)方法,后續(xù)不再調(diào)用該方法。我們使用ehcache作緩存.
4.2.2.相關(guān)配置
pom.xml
引入shiro對ehcache的支持包和ehcache包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.5</version>
</dependency>
shiro.ini文件
#緩存管理器配置
cacheManager=org.apache.shiro.cache.ehcache.EhCacheManager
securityManager.cacheManager=$cacheManager
測試
@Test
public void test03(){
//對密碼進(jìn)行加密
ShiroUtil.login("admin",MD5Util.md5("123456","ak47"));
Subject subject = SecurityUtils.getSubject();
System.out.println(subject.hasRole("admin"));
System.out.println(subject.isPermitted("user:create"));
//退出登錄
subject.logout();
ShiroUtil.login("admin",MD5Util.md5("123456","ak47"));
Subject subject2 = SecurityUtils.getSubject();
System.out.println(subject2.hasRole("admin"));
System.out.println(subject2.isPermitted("user:create"));
}
//測試發(fā)現(xiàn),用戶登錄以后授權(quán)信息會(huì)被緩存,用戶退出以后緩存的授權(quán)信息清空
??寫在最后
以上內(nèi)容為
Shiro框架
的初步入門,以及一些Shiro權(quán)限
的小Demo案例代碼的學(xué)習(xí)過程中,手動(dòng)實(shí)踐才是最為重要的,多動(dòng)手遠(yuǎn)勝多看。文章來源:http://www.zghlxwxcb.cn/news/detail-472522.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-472522.html
到了這里,關(guān)于【Java】一文帶你快速入門Shiro權(quán)限框架的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!