一文總結(jié) Shiro 實(shí)戰(zhàn)教程
1.權(quán)限的管理
1.1 什么是權(quán)限管理
基本上涉及到用戶參與的系統(tǒng)都要進(jìn)行權(quán)限管理,權(quán)限管理屬于系統(tǒng)安全的范疇,權(quán)限管理實(shí)現(xiàn)對(duì)用戶訪問系統(tǒng)的控制,按照安全規(guī)則或者安全策略控制用戶可以訪問而且只能訪問自己被授權(quán)的資源。
權(quán)限管理包括用戶身份認(rèn)證和授權(quán)兩部分,簡(jiǎn)稱認(rèn)證授權(quán)。對(duì)于需要訪問控制的資源用戶首先經(jīng)過身份認(rèn)證,認(rèn)證通過后用戶具有該資源的訪問權(quán)限方可訪問。
1.2 什么是身份認(rèn)證
身份認(rèn)證,就是判斷一個(gè)用戶是否為合法用戶的處理過程。最常用的簡(jiǎn)單身份認(rèn)證方式是系統(tǒng)通過核對(duì)用戶輸入的用戶名和口令,看其是否與系統(tǒng)中存儲(chǔ)的該用戶的用戶名和口令一致,來判斷用戶身份是否正確。對(duì)于采用指紋等系統(tǒng),則出示指紋;對(duì)于硬件Key等刷卡系統(tǒng),則需要刷卡。
1.3 什么是授權(quán)
授權(quán),即訪問控制,控制誰能訪問哪些資源。主體進(jìn)行身份認(rèn)證后需要分配權(quán)限方可訪問系統(tǒng)的資源,對(duì)于某些資源沒有權(quán)限是無法訪問的
2.什么是shiro
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.
Shiro 是一個(gè)功能強(qiáng)大且易于使用的Java安全框架,它執(zhí)行身份驗(yàn)證、授權(quán)、加密和會(huì)話管理。使用Shiro易于理解的API,您可以快速輕松地保護(hù)任何應(yīng)用程序—從最小的移動(dòng)應(yīng)用程序到最大的web和企業(yè)應(yīng)用程序。
Shiro是apache旗下一個(gè)開源框架,它將軟件系統(tǒng)的安全認(rèn)證相關(guān)的功能抽取出來,實(shí)現(xiàn)用戶身份認(rèn)證,權(quán)限授權(quán)、加密、會(huì)話管理等功能,組成了一個(gè)通用的安全認(rèn)證框架。
3.shiro的核心架構(gòu)
3.1 Subject
Subject即主體
,外部應(yīng)用與subject進(jìn)行交互,subject記錄了當(dāng)前操作用戶,將用戶的概念理解為當(dāng)前操作的主體,可能是一個(gè)通過瀏覽器請(qǐng)求的用戶,也可能是一個(gè)運(yùn)行的程序。 Subject在shiro中是一個(gè)接口,接口中定義了很多認(rèn)證授權(quán)相關(guān)的方法,外部程序通過subject進(jìn)行認(rèn)證授權(quán),而subject是通過SecurityManager安全管理器進(jìn)行認(rèn)證授權(quán)
3.2 SecurityManager
SecurityManager即安全管理器
,對(duì)全部的subject進(jìn)行安全管理,它是shiro的核心,負(fù)責(zé)對(duì)所有的subject進(jìn)行安全管理。通過SecurityManager可以完成subject的認(rèn)證、授權(quán)等,實(shí)質(zhì)上SecurityManager是通過Authenticator進(jìn)行認(rèn)證,通過Authorizer進(jìn)行授權(quán),通過SessionManager進(jìn)行會(huì)話管理等。
SecurityManager是一個(gè)接口,繼承了Authenticator, Authorizer, SessionManager這三個(gè)接口。
3.3 Authenticator
Authenticator即認(rèn)證器
,對(duì)用戶身份進(jìn)行認(rèn)證,Authenticator是一個(gè)接口,shiro提供ModularRealmAuthenticator實(shí)現(xiàn)類,通過ModularRealmAuthenticator基本上可以滿足大多數(shù)需求,也可以自定義認(rèn)證器。
3.4 Authorizer
Authorizer即授權(quán)器,用戶通過認(rèn)證器認(rèn)證通過,在訪問功能時(shí)需要通過授權(quán)器判斷用戶是否有此功能的操作權(quán)限。
3.5 Realm
Realm即領(lǐng)域,相當(dāng)于datasource數(shù)據(jù)源,securityManager進(jìn)行安全認(rèn)證需要通過Realm獲取用戶權(quán)限數(shù)據(jù),比如:如果用戶身份數(shù)據(jù)在數(shù)據(jù)庫(kù)那么realm就需要從數(shù)據(jù)庫(kù)獲取用戶身份信息。
- ? 注意:不要把realm理解成只是從數(shù)據(jù)源取數(shù)據(jù),在realm中還有認(rèn)證授權(quán)校驗(yàn)的相關(guān)的代碼。
3.6 SessionManager
sessionManager即會(huì)話管理,shiro框架定義了一套會(huì)話管理,它不依賴web容器的session,所以shiro可以使用在非web應(yīng)用上,也可以將分布式應(yīng)用的會(huì)話集中在一點(diǎn)管理,此特性可使它實(shí)現(xiàn)單點(diǎn)登錄。
3.7 SessionDAO
SessionDAO即會(huì)話dao,是對(duì)session會(huì)話操作的一套接口,比如要將session存儲(chǔ)到數(shù)據(jù)庫(kù),可以通過jdbc將會(huì)話存儲(chǔ)到數(shù)據(jù)庫(kù)。
3.8 CacheManager
CacheManager即緩存管理,將用戶權(quán)限數(shù)據(jù)存儲(chǔ)在緩存,這樣可以提高性能。
3.9 Cryptography
? Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開發(fā)。比如提供常用的散列、加/解密等功能。
4. shiro中的認(rèn)證
4.1 認(rèn)證
身份認(rèn)證,就是判斷一個(gè)用戶是否為合法用戶的處理過程。最常用的簡(jiǎn)單身份認(rèn)證方式是系統(tǒng)通過核對(duì)用戶輸入的用戶名和口令,看其是否與系統(tǒng)中存儲(chǔ)的該用戶的用戶名和口令一致,來判斷用戶身份是否正確。
4.2 shiro中認(rèn)證的關(guān)鍵對(duì)象
- Subject:主體
訪問系統(tǒng)的用戶,主體可以是用戶、程序等,進(jìn)行認(rèn)證的都稱為主體;
- Principal:身份信息
是主體(subject)進(jìn)行身份認(rèn)證的標(biāo)識(shí),標(biāo)識(shí)必須具有唯一性,如用戶名、手機(jī)號(hào)、郵箱地址等,一個(gè)主體可以有多個(gè)身份,但是必須有一個(gè)主身份(Primary Principal)。
- credential:憑證信息
是只有主體自己知道的安全信息,如密碼、證書等。
4.3 認(rèn)證流程
4.4 認(rèn)證的開發(fā)
1. 創(chuàng)建項(xiàng)目并引入依賴
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.5.3</version> </dependency>
2. 引入shiro配置文件并加入如下配置
[users] mosin=1234 tom=1234
3.開發(fā)認(rèn)證代碼
/** * @author: mosin * @version: v1.0 */ public class ShiroTest { public static void main(String[] args) { //創(chuàng)建默認(rèn)的安全管理器 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); //創(chuàng)建安全管理器需要的realm對(duì)象 IniRealm iniRealm = new IniRealm("classpath:realm.ini"); //安全管理器設(shè)置realm對(duì)象 defaultSecurityManager.setRealm(iniRealm); //將安全管理器注入安全工具類 用于獲取認(rèn)證的主體 SecurityUtils.setSecurityManager(defaultSecurityManager); //獲取認(rèn)證的主體 Subject subject = SecurityUtils.getSubject(); //創(chuàng)建令牌 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("mosin", "1234"); try { //認(rèn)證 通過沒有任何的異常 subject.login(usernamePasswordToken); //驗(yàn)證是否通過 boolean authenticated = subject.isAuthenticated(); System.out.println("認(rèn)證通過:"+authenticated); } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("用戶名錯(cuò)誤!"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密碼錯(cuò)誤!"); } } }
- DisabledAccountException(帳號(hào)被禁用)
- LockedAccountException(帳號(hào)被鎖定)
- ExcessiveAttemptsException(登錄失敗次數(shù)過多)
- ExpiredCredentialsException(憑證過期)等
4.5 自定義Realm
上邊的程序使用的是Shiro自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息,大部分情況下需要從系統(tǒng)的數(shù)據(jù)庫(kù)中讀取用戶信息,所以需要自定義realm。
1.shiro提供的Realm
2.根據(jù)認(rèn)證源碼認(rèn)證使用的是SimpleAccountRealm
SimpleAccountRealm的部分源碼中有兩個(gè)方法一個(gè)是 認(rèn)證 一個(gè)是 授權(quán)
,
public class SimpleAccountRealm extends AuthorizingRealm { protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; SimpleAccount account = getUser(upToken.getUsername()); if (account != null) { if (account.isLocked()) { throw new LockedAccountException("Account [" + account + "] is locked."); } if (account.isCredentialsExpired()) { String msg = "The credentials for account [" + account + "] are expired"; throw new ExpiredCredentialsException(msg); } } return account; } protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = getUsername(principals); USERS_LOCK.readLock().lock(); try { return this.users.get(username); } finally { USERS_LOCK.readLock().unlock(); } } }
3.自定義realm
/** * 自定義realm */ public class CustomerRealm extends AuthorizingRealm { //認(rèn)證方法 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } //授權(quán)方法 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String principal = (String) token.getPrincipal(); if("mosin".equals(principal)){ return new SimpleAuthenticationInfo(principal,"123",this.getName()); } return null; } }
4.使用自定義Realm認(rèn)證
public class TestAuthenticatorCustomerRealm { public static void main(String[] args) { //創(chuàng)建securityManager DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); //IniRealm realm = new IniRealm("classpath:realm.ini"); //設(shè)置為自定義realm獲取認(rèn)證數(shù)據(jù) defaultSecurityManager.setRealm(new CustomerRealm()); //將安裝工具類中設(shè)置默認(rèn)安全管理器 SecurityUtils.setSecurityManager(defaultSecurityManager); //獲取主體對(duì)象 Subject subject = SecurityUtils.getSubject(); //創(chuàng)建token令牌 UsernamePasswordToken token = new UsernamePasswordToken("mosin", "1234"); try { subject.login(token);//用戶登錄 System.out.println("登錄成功"); } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("用戶名錯(cuò)誤!!"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密碼錯(cuò)誤!!!"); } } }
4.6 使用MD5和Salt
實(shí)際應(yīng)用是將鹽和散列后的值存在數(shù)據(jù)庫(kù)中,自動(dòng)realm從數(shù)據(jù)庫(kù)取出鹽和加密后的值由shiro完成密碼校驗(yàn)。
1.自定義md5+salt的realm
/** * 自定義md5+salt realm */ public class CustomerMD5Realm extends AuthorizingRealm { //授權(quán) protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } //認(rèn)證 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String principal = (String) token.getPrincipal(); //根據(jù)用戶名查詢數(shù)據(jù)庫(kù) if("mosin".equals(principal)){ // 參數(shù)1:用戶名 參數(shù)2:密碼 參數(shù)3:鹽 參數(shù)4:自定義realm的名字 System.out.println(this.getName()); return new SimpleAuthenticationInfo(principal, "800d63a19662b2ba95bc2ffa01ab4804", ByteSource.Util.bytes("mosin"),this.getName()); } return null; } }
2.使用md5 + salt 認(rèn)證
public class CustomerMD5RealmTest { public static void main(String[] args) { //創(chuàng)建安全管理器 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); //創(chuàng)建自定義MD5Realm對(duì)象 CustomerMD5Realm customerMD5Realm = new CustomerMD5Realm(); //創(chuàng)建密碼認(rèn)證匹配器對(duì)象 HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("MD5"); //設(shè)置散列的次數(shù) md5.setHashIterations(1024); //設(shè)置密碼認(rèn)證匹配器對(duì)象 customerMD5Realm.setCredentialsMatcher(md5); //設(shè)置安全管理器的 認(rèn)證安全數(shù)據(jù)源 defaultSecurityManager.setRealm(customerMD5Realm); //設(shè)置安全工具類的安全管理器 SecurityUtils.setSecurityManager(defaultSecurityManager); //獲取認(rèn)證的主體 Subject subject = SecurityUtils.getSubject(); //創(chuàng)建令牌 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("mosi", "12345"); //登錄認(rèn)證 try { subject.login(usernamePasswordToken); System.out.println("認(rèn)證通過:"+subject.isAuthenticated()); } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("用戶名錯(cuò)誤"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密碼錯(cuò)誤!!!"); } } }
5. shiro中的授權(quán)
5.1 授權(quán)
授權(quán),即訪問控制,控制誰能訪問哪些資源。主體進(jìn)行身份認(rèn)證后需要分配權(quán)限方可訪問系統(tǒng)的資源,對(duì)于某些資源沒有權(quán)限是無法訪問的。
5.2 關(guān)鍵對(duì)象
授權(quán)可簡(jiǎn)單理解為who對(duì)what(which)進(jìn)行How操作:
Who,即主體(Subject)
,主體需要訪問系統(tǒng)中的資源。
What,即資源(Resource)
,如系統(tǒng)菜單、頁面、按鈕、類方法、系統(tǒng)商品信息等。資源包括資源類型
和資源實(shí)例,比如商品信息為資源類型,類型為t01的商品為資源實(shí)例
,編號(hào)為001的商品信息也屬于資源實(shí)例。
How,權(quán)限/許可(Permission)
,規(guī)定了主體對(duì)資源的操作許可,權(quán)限離開資源沒有意義,如用戶查詢權(quán)限、用戶添加權(quán)限、某個(gè)類方法的調(diào)用權(quán)限、編號(hào)為001用戶的修改權(quán)限等,通過權(quán)限可知主體對(duì)哪些資源都有哪些操作許可。
5.3 授權(quán)流程
5.4 授權(quán)方式
- 基于角色的訪問控制
- RBAC基于角色的訪問控制(Role-Based Access Control)是以角色為中心進(jìn)行訪問控制
- 基于資源的訪問控制
- RBAC基于資源的訪問控制(Resource-Based Access Control)是以資源為中心進(jìn)行訪問控制
if(subject.isPermission("user:update:01")){ //資源實(shí)例 //對(duì)01用戶進(jìn)行修改 } if(subject.isPermission("user:update:*")){ //資源類型 //對(duì)01用戶進(jìn)行修改 }
5.5 權(quán)限字符串
? 權(quán)限字符串的規(guī)則是:資源標(biāo)識(shí)符:操作:資源實(shí)例標(biāo)識(shí)符,意思是對(duì)哪個(gè)資源的哪個(gè)實(shí)例具有什么操作,“:”是資源/操作/實(shí)例的分割符,權(quán)限字符串也可以使用*通配符。
例子:
- 用戶創(chuàng)建權(quán)限:user:create,或user:create:*
- 用戶修改實(shí)例001的權(quán)限:user:update:001
- 用戶實(shí)例001的所有權(quán)限:user:*:001
5.6 shiro中授權(quán)編程實(shí)現(xiàn)方式
- 編程式
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(“admin”)) { //有權(quán)限 } else { //無權(quán)限 }
- 注解式
@RequiresRoles("admin") public void hello() { //有權(quán)限 }
- 標(biāo)簽式
JSP/GSP 標(biāo)簽:在JSP/GSP 頁面通過相應(yīng)的標(biāo)簽完成: <shiro:hasRole name="admin"> <!— 有權(quán)限—> </shiro:hasRole> 注意: Thymeleaf 中使用shiro需要額外集成!
5.7 開發(fā)授權(quán)
1.realm的實(shí)現(xiàn)
public class CustomerRealm extends AuthorizingRealm { //授權(quán) @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String primaryPrincipal = (String) principals.getPrimaryPrincipal(); System.out.println("primaryPrincipal = " + primaryPrincipal); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRole("admin"); simpleAuthorizationInfo.addStringPermission("user:update:*"); simpleAuthorizationInfo.addStringPermission("product:*:*"); return simpleAuthorizationInfo; } //認(rèn)證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String principal = (String) token.getPrincipal(); if("xiaochen".equals(principal)){ String password = "3c88b338102c1a343bcb88cd3878758e"; String salt = "Q4F%"; return new SimpleAuthenticationInfo(principal,password, ByteSource.Util.bytes(salt),this.getName()); } return null; } }
2.授權(quán)
public class CustomerMD5RealmTest { public static void main(String[] args) { //創(chuàng)建安全管理器 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); //創(chuàng)建自定義MD5Realm對(duì)象 CustomerMD5Realm customerMD5Realm = new CustomerMD5Realm(); //創(chuàng)建密碼認(rèn)證匹配器對(duì)象 HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("md5"); //設(shè)置加密的次數(shù) md5.setHashIterations(1024); //設(shè)置密碼認(rèn)證匹配器對(duì)象 customerMD5Realm.setCredentialsMatcher(md5); //設(shè)置安全管理器的 認(rèn)證安全數(shù)據(jù)源 defaultSecurityManager.setRealm(customerMD5Realm); //設(shè)置安全工具類的安全管理器 SecurityUtils.setSecurityManager(defaultSecurityManager); //獲取認(rèn)證的主體 Subject subject = SecurityUtils.getSubject(); //創(chuàng)建令牌 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("mosin", "12345"); //登錄認(rèn)證 try { subject.login(usernamePasswordToken); System.out.println("認(rèn)證通過:"+subject.isAuthenticated()); } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("用戶名錯(cuò)誤"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密碼錯(cuò)誤!!!"); } //基于角色的控制 //單角色控制 System.out.println("========hasRole=========="); boolean admin = subject.hasRole("admin"); System.out.println("hash admin role:"+admin); //多角色控制 System.out.println("========hasAllRoles=========="); List<String> roles = Arrays.asList("admin", "user"); boolean booleans = subject.hasAllRoles(roles); System.out.println("booleans = " + booleans); // 基于任意角色的控制 System.out.println("========hasRoles=========="); boolean[] booleans1 = subject.hasRoles(roles); for (boolean b : booleans1) { System.out.println("b = " + b); } //基于權(quán)限字符串的權(quán)限控制 System.out.println("========isPermitted=========="); boolean permitted = subject.isPermitted("user:update:*"); System.out.println("permitted = " + permitted); //分別具有哪些權(quán)限 boolean[] permitted1 = subject.isPermitted("user:update:*", "product:update:*"); for (boolean b : permitted1) { System.out.println("b = " + b); } //同時(shí)具有哪些權(quán)限 boolean permittedAll = subject.isPermittedAll("user:update:*", "product:update:*"); System.out.println("permittedAll = " + permittedAll); } }
以上就是一文總結(jié) Shiro 實(shí)戰(zhàn)教程的詳細(xì)內(nèi)容,更多關(guān)于Java Shiro實(shí)戰(zhàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)現(xiàn)字符串的分割(基于String.split()方法)
Java中的我們可以利用split把字符串按照指定的分割符進(jìn)行分割,然后返回字符串?dāng)?shù)組,下面這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)字符串的分割的相關(guān)資料,是基于jDK1.8版本中的String.split()方法,需要的朋友可以參考下2022-09-09詳解SpringBoot中使用JPA作為數(shù)據(jù)持久化框架
這篇文章主要介紹了SpringBoot中使用JPA作為數(shù)據(jù)持久化框架的相關(guān)知識(shí),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03在Java中將double轉(zhuǎn)換為int的操作方法
這篇文章主要介紹了在Java中將double轉(zhuǎn)換為int的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03Java棧的應(yīng)用之括號(hào)匹配算法實(shí)例分析
這篇文章主要介紹了Java棧的應(yīng)用之括號(hào)匹配算法,結(jié)合實(shí)例形式分析了Java使用棧實(shí)現(xiàn)括號(hào)匹配算法的相關(guān)原理、操作技巧與注意事項(xiàng),需要的朋友可以參考下2020-03-03基于Protobuf動(dòng)態(tài)解析在Java中的應(yīng)用 包含例子程序
下面小編就為大家?guī)硪黄赑rotobuf動(dòng)態(tài)解析在Java中的應(yīng)用 包含例子程序。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07java 定義長(zhǎng)度為0的數(shù)組/空數(shù)組案例
這篇文章主要介紹了java 定義長(zhǎng)度為0的數(shù)組/空數(shù)組案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-03-03