一文總結 Shiro 實戰(zhàn)教程
1.權限的管理
1.1 什么是權限管理
基本上涉及到用戶參與的系統(tǒng)都要進行權限管理,權限管理屬于系統(tǒng)安全的范疇,權限管理實現(xiàn)對用戶訪問系統(tǒng)的控制,按照安全規(guī)則或者安全策略控制用戶可以訪問而且只能訪問自己被授權的資源。
權限管理包括用戶身份認證和授權兩部分,簡稱認證授權。對于需要訪問控制的資源用戶首先經(jīng)過身份認證,認證通過后用戶具有該資源的訪問權限方可訪問。
1.2 什么是身份認證
身份認證,就是判斷一個用戶是否為合法用戶的處理過程。最常用的簡單身份認證方式是系統(tǒng)通過核對用戶輸入的用戶名和口令,看其是否與系統(tǒng)中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確。對于采用指紋等系統(tǒng),則出示指紋;對于硬件Key等刷卡系統(tǒng),則需要刷卡。
1.3 什么是授權
授權,即訪問控制,控制誰能訪問哪些資源。主體進行身份認證后需要分配權限方可訪問系統(tǒng)的資源,對于某些資源沒有權限是無法訪問的
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 是一個功能強大且易于使用的Java安全框架,它執(zhí)行身份驗證、授權、加密和會話管理。使用Shiro易于理解的API,您可以快速輕松地保護任何應用程序—從最小的移動應用程序到最大的web和企業(yè)應用程序。
Shiro是apache旗下一個開源框架,它將軟件系統(tǒng)的安全認證相關的功能抽取出來,實現(xiàn)用戶身份認證,權限授權、加密、會話管理等功能,組成了一個通用的安全認證框架。
3.shiro的核心架構

3.1 Subject
Subject即主體,外部應用與subject進行交互,subject記錄了當前操作用戶,將用戶的概念理解為當前操作的主體,可能是一個通過瀏覽器請求的用戶,也可能是一個運行的程序。 Subject在shiro中是一個接口,接口中定義了很多認證授權相關的方法,外部程序通過subject進行認證授權,而subject是通過SecurityManager安全管理器進行認證授權
3.2 SecurityManager
SecurityManager即安全管理器,對全部的subject進行安全管理,它是shiro的核心,負責對所有的subject進行安全管理。通過SecurityManager可以完成subject的認證、授權等,實質上SecurityManager是通過Authenticator進行認證,通過Authorizer進行授權,通過SessionManager進行會話管理等。
SecurityManager是一個接口,繼承了Authenticator, Authorizer, SessionManager這三個接口。
3.3 Authenticator
Authenticator即認證器,對用戶身份進行認證,Authenticator是一個接口,shiro提供ModularRealmAuthenticator實現(xiàn)類,通過ModularRealmAuthenticator基本上可以滿足大多數(shù)需求,也可以自定義認證器。
3.4 Authorizer
Authorizer即授權器,用戶通過認證器認證通過,在訪問功能時需要通過授權器判斷用戶是否有此功能的操作權限。
3.5 Realm
Realm即領域,相當于datasource數(shù)據(jù)源,securityManager進行安全認證需要通過Realm獲取用戶權限數(shù)據(jù),比如:如果用戶身份數(shù)據(jù)在數(shù)據(jù)庫那么realm就需要從數(shù)據(jù)庫獲取用戶身份信息。
- ? 注意:不要把realm理解成只是從數(shù)據(jù)源取數(shù)據(jù),在realm中還有認證授權校驗的相關的代碼。
3.6 SessionManager
sessionManager即會話管理,shiro框架定義了一套會話管理,它不依賴web容器的session,所以shiro可以使用在非web應用上,也可以將分布式應用的會話集中在一點管理,此特性可使它實現(xiàn)單點登錄。
3.7 SessionDAO
SessionDAO即會話dao,是對session會話操作的一套接口,比如要將session存儲到數(shù)據(jù)庫,可以通過jdbc將會話存儲到數(shù)據(jù)庫。
3.8 CacheManager
CacheManager即緩存管理,將用戶權限數(shù)據(jù)存儲在緩存,這樣可以提高性能。
3.9 Cryptography
? Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開發(fā)。比如提供常用的散列、加/解密等功能。
4. shiro中的認證
4.1 認證
身份認證,就是判斷一個用戶是否為合法用戶的處理過程。最常用的簡單身份認證方式是系統(tǒng)通過核對用戶輸入的用戶名和口令,看其是否與系統(tǒng)中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確。
4.2 shiro中認證的關鍵對象
- Subject:主體
訪問系統(tǒng)的用戶,主體可以是用戶、程序等,進行認證的都稱為主體;
- Principal:身份信息
是主體(subject)進行身份認證的標識,標識必須具有唯一性,如用戶名、手機號、郵箱地址等,一個主體可以有多個身份,但是必須有一個主身份(Primary Principal)。
- credential:憑證信息
是只有主體自己知道的安全信息,如密碼、證書等。
4.3 認證流程

4.4 認證的開發(fā)
1. 創(chuà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ā)認證代碼
/**
* @author: mosin
* @version: v1.0
*/
public class ShiroTest {
public static void main(String[] args) {
//創(chuàng)建默認的安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//創(chuàng)建安全管理器需要的realm對象
IniRealm iniRealm = new IniRealm("classpath:realm.ini");
//安全管理器設置realm對象
defaultSecurityManager.setRealm(iniRealm);
//將安全管理器注入安全工具類 用于獲取認證的主體
SecurityUtils.setSecurityManager(defaultSecurityManager);
//獲取認證的主體
Subject subject = SecurityUtils.getSubject();
//創(chuàng)建令牌
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("mosin", "1234");
try {
//認證 通過沒有任何的異常
subject.login(usernamePasswordToken);
//驗證是否通過
boolean authenticated = subject.isAuthenticated();
System.out.println("認證通過:"+authenticated);
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用戶名錯誤!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密碼錯誤!");
}
}
}
- DisabledAccountException(帳號被禁用)
- LockedAccountException(帳號被鎖定)
- ExcessiveAttemptsException(登錄失敗次數(shù)過多)
- ExpiredCredentialsException(憑證過期)等
4.5 自定義Realm
上邊的程序使用的是Shiro自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息,大部分情況下需要從系統(tǒng)的數(shù)據(jù)庫中讀取用戶信息,所以需要自定義realm。
1.shiro提供的Realm

2.根據(jù)認證源碼認證使用的是SimpleAccountRealm

SimpleAccountRealm的部分源碼中有兩個方法一個是 認證 一個是 授權,
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 {
//認證方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//授權方法
@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認證
public class TestAuthenticatorCustomerRealm {
public static void main(String[] args) {
//創(chuàng)建securityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//IniRealm realm = new IniRealm("classpath:realm.ini");
//設置為自定義realm獲取認證數(shù)據(jù)
defaultSecurityManager.setRealm(new CustomerRealm());
//將安裝工具類中設置默認安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
//獲取主體對象
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("用戶名錯誤!!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密碼錯誤!!!");
}
}
}
4.6 使用MD5和Salt
實際應用是將鹽和散列后的值存在數(shù)據(jù)庫中,自動realm從數(shù)據(jù)庫取出鹽和加密后的值由shiro完成密碼校驗。
1.自定義md5+salt的realm
/**
* 自定義md5+salt realm
*/
public class CustomerMD5Realm extends AuthorizingRealm {
//授權
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//認證
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
//根據(jù)用戶名查詢數(shù)據(jù)庫
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 認證
public class CustomerMD5RealmTest {
public static void main(String[] args) {
//創(chuàng)建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//創(chuàng)建自定義MD5Realm對象
CustomerMD5Realm customerMD5Realm = new CustomerMD5Realm();
//創(chuàng)建密碼認證匹配器對象
HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("MD5");
//設置散列的次數(shù)
md5.setHashIterations(1024);
//設置密碼認證匹配器對象
customerMD5Realm.setCredentialsMatcher(md5);
//設置安全管理器的 認證安全數(shù)據(jù)源
defaultSecurityManager.setRealm(customerMD5Realm);
//設置安全工具類的安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
//獲取認證的主體
Subject subject = SecurityUtils.getSubject();
//創(chuàng)建令牌
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("mosi", "12345");
//登錄認證
try {
subject.login(usernamePasswordToken);
System.out.println("認證通過:"+subject.isAuthenticated());
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用戶名錯誤");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密碼錯誤!!!");
}
}
}
5. shiro中的授權
5.1 授權
授權,即訪問控制,控制誰能訪問哪些資源。主體進行身份認證后需要分配權限方可訪問系統(tǒng)的資源,對于某些資源沒有權限是無法訪問的。
5.2 關鍵對象
授權可簡單理解為who對what(which)進行How操作:
Who,即主體(Subject),主體需要訪問系統(tǒng)中的資源。
What,即資源(Resource),如系統(tǒng)菜單、頁面、按鈕、類方法、系統(tǒng)商品信息等。資源包括資源類型和資源實例,比如商品信息為資源類型,類型為t01的商品為資源實例,編號為001的商品信息也屬于資源實例。
How,權限/許可(Permission),規(guī)定了主體對資源的操作許可,權限離開資源沒有意義,如用戶查詢權限、用戶添加權限、某個類方法的調用權限、編號為001用戶的修改權限等,通過權限可知主體對哪些資源都有哪些操作許可。
5.3 授權流程

5.4 授權方式
- 基于角色的訪問控制
- RBAC基于角色的訪問控制(Role-Based Access Control)是以角色為中心進行訪問控制
- 基于資源的訪問控制
- RBAC基于資源的訪問控制(Resource-Based Access Control)是以資源為中心進行訪問控制
if(subject.isPermission("user:update:01")){ //資源實例
//對01用戶進行修改
}
if(subject.isPermission("user:update:*")){ //資源類型
//對01用戶進行修改
}
5.5 權限字符串
? 權限字符串的規(guī)則是:資源標識符:操作:資源實例標識符,意思是對哪個資源的哪個實例具有什么操作,“:”是資源/操作/實例的分割符,權限字符串也可以使用*通配符。
例子:
- 用戶創(chuàng)建權限:user:create,或user:create:*
- 用戶修改實例001的權限:user:update:001
- 用戶實例001的所有權限:user:*:001
5.6 shiro中授權編程實現(xiàn)方式
- 編程式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有權限
} else {
//無權限
}
- 注解式
@RequiresRoles("admin")
public void hello() {
//有權限
}
- 標簽式
JSP/GSP 標簽:在JSP/GSP 頁面通過相應的標簽完成: <shiro:hasRole name="admin"> <!— 有權限—> </shiro:hasRole> 注意: Thymeleaf 中使用shiro需要額外集成!
5.7 開發(fā)授權
1.realm的實現(xiàn)
public class CustomerRealm extends AuthorizingRealm {
//授權
@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;
}
//認證
@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.授權
public class CustomerMD5RealmTest {
public static void main(String[] args) {
//創(chuàng)建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//創(chuàng)建自定義MD5Realm對象
CustomerMD5Realm customerMD5Realm = new CustomerMD5Realm();
//創(chuàng)建密碼認證匹配器對象
HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("md5");
//設置加密的次數(shù)
md5.setHashIterations(1024);
//設置密碼認證匹配器對象
customerMD5Realm.setCredentialsMatcher(md5);
//設置安全管理器的 認證安全數(shù)據(jù)源
defaultSecurityManager.setRealm(customerMD5Realm);
//設置安全工具類的安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
//獲取認證的主體
Subject subject = SecurityUtils.getSubject();
//創(chuàng)建令牌
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("mosin", "12345");
//登錄認證
try {
subject.login(usernamePasswordToken);
System.out.println("認證通過:"+subject.isAuthenticated());
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用戶名錯誤");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密碼錯誤!!!");
}
//基于角色的控制
//單角色控制
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);
}
//基于權限字符串的權限控制
System.out.println("========isPermitted==========");
boolean permitted = subject.isPermitted("user:update:*");
System.out.println("permitted = " + permitted);
//分別具有哪些權限
boolean[] permitted1 = subject.isPermitted("user:update:*", "product:update:*");
for (boolean b : permitted1) {
System.out.println("b = " + b);
}
//同時具有哪些權限
boolean permittedAll = subject.isPermittedAll("user:update:*", "product:update:*");
System.out.println("permittedAll = " + permittedAll);
}
}
以上就是一文總結 Shiro 實戰(zhàn)教程的詳細內容,更多關于Java Shiro實戰(zhàn)的資料請關注腳本之家其它相關文章!
相關文章
Java實現(xiàn)字符串的分割(基于String.split()方法)
Java中的我們可以利用split把字符串按照指定的分割符進行分割,然后返回字符串數(shù)組,下面這篇文章主要給大家介紹了關于Java實現(xiàn)字符串的分割的相關資料,是基于jDK1.8版本中的String.split()方法,需要的朋友可以參考下2022-09-09
詳解SpringBoot中使用JPA作為數(shù)據(jù)持久化框架
這篇文章主要介紹了SpringBoot中使用JPA作為數(shù)據(jù)持久化框架的相關知識,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
基于Protobuf動態(tài)解析在Java中的應用 包含例子程序
下面小編就為大家?guī)硪黄赑rotobuf動態(tài)解析在Java中的應用 包含例子程序。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
java 定義長度為0的數(shù)組/空數(shù)組案例
這篇文章主要介紹了java 定義長度為0的數(shù)組/空數(shù)組案例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03

