Springboot繼承Shiro
Springboot中的Shiro框架
首先了解下原理,了解下shrio的認證的邏輯,再講解下springboot中,如何通過代碼進行認證,授權操作。
Shiro框架的邏輯
RBAC模型
在講解認證授權之前,先介紹下RBAC模型,Shiro框架后續用上的最后本質上,還是通過查詢這個庫。
-
定義:RBAC(Role-Based Access Control)即基于角色的訪問控制模型,核心是通過 “用戶 - 角色 - 權限” 的層級關系實現訪問控制,簡化權限管理流程。
-
核心要素
:
- 用戶(User):系統的實際使用者,可被分配多個角色。
- 角色(Role):一組相關權限的集合,代表用戶的職能或職位(如管理員、普通用戶)。
- 權限(Permission):對系統資源的操作許可(如查看、編輯、刪除等),可關聯到角色。
- 關系:用戶與角色、角色與權限均為多對多關系。
如何做到不同的角色有不同的權限。
sys_menu(權限 / 菜單表)
存儲系統中所有可操作的權限(目錄、菜單、按鈕),對應 “創建文檔”“查看文檔” 等具體權限。
| id | parent_id | title | path | perms | component | type | created | sort_order | icon | status | updated |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 0 | 文檔管理 | /document | NULL | Layout | 0 | 2024-01-01 00:00:00 | 1 | folder | 0 | 2024-01-01 00:00:00 |
| 2 | 1 | 文檔列表 | /document/list | document:list | DocumentList | 1 | 2024-01-01 00:00:00 | 2 | list | 0 | 2024-01-01 00:00:00 |
| 3 | 2 | 創建文檔 | NULL | document:create | NULL | 2 | 2024-01-01 00:00:00 | 3 | plus | 0 | 2024-01-01 00:00:00 |
| 4 | 2 | 查看文檔 | NULL | document:view | NULL | 2 | 2024-01-01 00:00:00 | 4 | eye | 0 | 2024-01-01 00:00:00 |
| 5 | 2 | 編輯文檔 | NULL | document:edit | NULL | 2 | 2024-01-01 00:00:00 | 5 | edit | 0 | 2024-01-01 00:00:00 |
| 6 | 2 | 刪除文檔 | NULL | document:delete | NULL | 2 | 2024-01-01 00:00:00 | 6 | trash | 0 | 2024-01-01 00:00:00 |
| 7 | 0 | 系統管理 | /system | NULL | Layout | 0 | 2024-01-01 00:00:00 | 7 | setting | 0 | 2024-01-01 00:00:00 |
| 8 | 7 | 用戶管理 | /system/user | user:manage | UserManage | 1 | 2024-01-01 00:00:00 | 8 | user | 0 | 2024-01-01 00:00:00 |
解釋:
-
type字段區分權限類型:0 = 目錄(如 “文檔管理”“系統管理”)、1 = 菜單(如 “文檔列表”“用戶管理”)、2 = 按鈕(如 “創建文檔”“刪除文檔”)。 -
perms字段對應具體操作權限,與前文的 P1-P5 對應關系:
- P1(創建文檔)→
document:create - P2(查看文檔)→
document:view - P3(編輯文檔)→
document:edit - P4(刪除文檔)→
document:delete - P5(管理用戶)→
user:manage
- P1(創建文檔)→
sys_role(角色表)
存儲系統中的角色,對應 “Admin、Manager、Employee、Guest”。
| id | name | code | remark | created | updated |
|---|---|---|---|---|---|
| 1 | 超級管理員 | ROLE_ADMIN | 擁有系統所有權限 | 2024-01-01 00:00:00 | 2024-01-01 00:00:00 |
| 2 | 部門經理 | ROLE_MANAGER | 擁有文檔全操作權限 | 2024-01-01 00:00:00 | 2024-01-01 00:00:00 |
| 3 | 普通員工 | ROLE_EMPLOYEE | 擁有文檔創建 / 查看 / 編輯權限 | 2024-01-01 00:00:00 | 2024-01-01 00:00:00 |
| 4 | 訪客 | ROLE_GUEST | 僅擁有文檔查看權限 | 2024-01-01 00:00:00 | 2024-01-01 00:00:00 |
解釋:
code字段為角色標識,用于權限校驗(如ROLE_ADMIN對應管理員)。- 角色權限范圍與前文一致:Admin > Manager > Employee > Guest。
sys_role_menu(角色 - 權限關聯表)
關聯角色與權限,定義每個角色可操作的具體權限。
| id | role_id | menu_id | |
|---|---|---|---|
| 1 | 1 | 3 | (Admin 擁有 “創建文檔” 權限) |
| 2 | 1 | 4 | (Admin 擁有 “查看文檔” 權限) |
| 3 | 1 | 5 | (Admin 擁有 “編輯文檔” 權限) |
| 4 | 1 | 6 | (Admin 擁有 “刪除文檔” 權限) |
| 5 | 1 | 8 | (Admin 擁有 “用戶管理” 權限) |
| 6 | 2 | 3 | (Manager 擁有 “創建文檔” 權限) |
| 7 | 2 | 4 | (Manager 擁有 “查看文檔” 權限) |
| 8 | 2 | 5 | (Manager 擁有 “編輯文檔” 權限) |
| 9 | 2 | 6 | (Manager 擁有 “刪除文檔” 權限) |
| 10 | 3 | 3 | (Employee 擁有 “創建文檔” 權限) |
| 11 | 3 | 4 | (Employee 擁有 “查看文檔” 權限) |
| 12 | 3 | 5 | (Employee 擁有 “編輯文檔” 權限) |
| 13 | 4 | 4 | (Guest 僅擁有 “查看文檔” 權限) |
解釋:
- 角色權限嚴格遵循前文規則:Admin 擁有所有權限(P1-P5),Manager 缺少 “管理用戶”(P5),Employee 缺少 “刪除文檔”(P4)和 “管理用戶”(P5),Guest 僅保留 “查看文檔”(P2)。
sys_user(用戶表)
存儲系統用戶信息,對應 “張三、李四、王五、訪客 001”。
| id | username | password | avatar | phone | created | updated | last_login | status | is_delete | |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | zhangsan | $2a$10$xxxxxx(加密后) | /avatar/zhangsan.jpg | zhangsan@example.com | 13800138000 | 2024-01-01 00:00:00 | NULL | 2024-07-16 09:00:00 | 0 | 0 |
| 2 | lisi | $2a$10$xxxxxx(加密后) | /avatar/lisi.jpg | lisi@example.com | 13900139000 | 2024-01-02 00:00:00 | NULL | 2024-07-16 09:30:00 | 0 | 0 |
| 3 | wangwu | $2a$10$xxxxxx(加密后) | /avatar/wangwu.jpg | wangwu@example.com | 13700137000 | 2024-01-03 00:00:00 | NULL | 2024-07-16 10:00:00 | 0 | 0 |
| 4 | guest001 | $2a$10$xxxxxx(加密后) | /avatar/guest.jpg | guest001@example.com | NULL | 2024-07-16 08:00:00 | NULL | 2024-07-16 08:30:00 | 0 | 0 |
解釋:
password字段存儲加密后的密碼(如 BCrypt 加密),避免明文泄露。status=0表示用戶正常,is_delete=0表示未刪除(邏輯刪除標記)。last_login記錄最近登錄時間,用于追蹤用戶活動。
sys_user_role(用戶 - 角色關聯表)
關聯用戶與角色,定義每個用戶所屬的角色。
| id | user_id | role_id | |
|---|---|---|---|
| 1 | 1 | 1 | (張三→超級管理員) |
| 2 | 2 | 2 | (李四→部門經理) |
| 3 | 3 | 3 | (王五→普通員工) |
| 4 | 4 | 4 | (訪客 001→訪客) |
解釋:
- 直接對應前文的用戶 - 角色關系,通過
user_id和role_id關聯,實現 “用戶→角色→權限” 的間接映射。
Shiro框架
Shiro 的功能可概括為四大基石及相關支持特性:
四大核心功能
- Authentication(認證):驗證用戶身份(如用戶名/密碼登錄、SSO登錄)
- Authorization(授權):細粒度的權限控制(如"user:delete"權限校驗)
- Session Management(會話管理):用戶特定的會話管理,支持非 Web/EJB 環境
- Cryptography(加密):提供易于使用的加密算法
支持特性
- Web支持:提供URL攔截、Remember Me等Web專屬功能
- 并發控制:支持多線程環境下的安全訪問
- 緩存機制:提升權限驗證性能(如EhCache集成)
- “記住我”:基于Cookie的持久化身份會話
- “運行方式”:允許特權用戶臨時扮演其他身份(Impersonation)
Shiro 架構組件
Shiro 架構主要包含三個核心概念:
-
Subject:當前用戶(可指人、第三方服務等任何與軟件交互的實體)
-
SecurityManager:管理所有 Subject,是 Shiro 架構的核心
-
Realm 是 Shiro 的核心安全數據訪問對象(Security DAO),它:
- 封裝了與安全數據源(數據庫、LDAP等)的連接細節
- 提供統一的API供 SecurityManager 調用
- 負責將數據源的原始數據轉換為 Shiro 可識別的安全信息
類比理解:就像JDBC連接數據庫,Realm是Shiro連接各種安全數據源的標準接口。這里不好解釋,就是類似存放了相關用戶數據。通過一些列操作之后得到相關的用戶的數據。
下面有關realm不了解可以先跳過,博主也暫時能力有限,沒有找到更好的表述方式,可以暫時先跳過。跟著流程表述就行,這里還是比較晦澀難懂。寫到這里博主覺得,可以先跟著后面的springboot是如何是如何使用shrio框架的進行過一遍流程。有不懂得地方再進行查閱,先弄懂相關的認證、授權的邏輯。先過一遍,后面再慢慢的一點的弄懂。先用著,再慢慢的了解其特性。
Realm 的基本概念
Realm(域)是 Shiro 框架中連接應用與安全數據源的 “橋梁” 或 “連接器”。當進行認證(登錄)和授權(訪問控制)驗證時,Shiro 會通過 Realm 獲取用戶及其權限信息。
其核心特點包括:
- 是 Shiro 的安全數據訪問層,相當于安全領域的 DAO
- 至少需要配置一個 Realm,也可配置多個
- 封裝了數據源的連接細節,提供統一訪問接口
- 支持多種數據源類型,可自定義實現
Realm 的核心功能
身份驗證getAuthenticationInfo方法
- 作用:驗證賬戶和密碼,返回用戶驗證信息
- 處理流程:
- 接收用戶提交的 Token(如用戶名和密碼)
- 從數據源獲取用戶存儲的驗證信息
- 比較 Token 與存儲的信息,完成驗證
- 返回驗證通過的用戶信息
- Token 示例(UsernamePasswordToken):
public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
private String username; // 用戶名
private char[] password; // 密碼
private boolean rememberMe; // 記住我
private String host; // 主機地址
}
權限獲取getAuthorizationInfo方法
- 作用:獲取指定用戶的角色和權限信息
- 功能:返回用戶被授予的角色集合和權限集合,
令牌支持supports方法
- 作用:判斷 Realm 是否支持某種類型的 Token
- 常用場景:通常支持 UsernamePasswordToken,也可擴展支持其他類型(如 HostAuthenticationToken)
Realm 的方法執行時機
身份驗證方法getAuthenticationInfo
- 觸發時機:當調用
subject.login(token)時執行 - 示例代碼中的觸發點:
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token); // 此時觸發身份驗證
權限獲取方法getAuthorizationInfo
- 觸發時機有三種:
- 調用
subject.hasRole("角色名")或subject.isPermitted("權限名")時 - 方法上使用
@RequiresRoles("角色名")等注解時 - 頁面中使用 Shiro 標簽(如
[@shiro.hasPermission name="權限名"][/@shiro.hasPermission])時
- 調用
shrio的認證授權
shrio認證過程
- 構建 SecurityManager 環境,并設置 Realm
- 主體(Subject)提交認證請求(調用 login 方法)
- SecurityManager 委托 Authenticator 進行身份驗證
- Authenticator 可能委托 AuthenticationStrategy 處理多 Realm 驗證
- Authenticator 將 token 傳入 Realm 獲取身份信息,無返回或拋出異常則認證失敗
流程觸發層
// 開發者可見的調用入口
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(new UsernamePasswordToken("admin", "password123"));
- 前置條件:必須通過
SecurityUtils.setSecurityManager()初始化安全管理器
核心控制層
SecurityManager作為中央調度器:
- 接收Subject提交的認證請求
- 委派給內置的
Authenticator組件執行具體驗證 - 管理整個認證過程的生命周期
認證執行層
Authenticator(默認實現ModularRealmAuthenticator):
@startuml
Authenticator -> AuthenticationStrategy : 應用驗證策略
AuthenticationStrategy -> Realm1 : 查詢憑證
AuthenticationStrategy -> Realm2 : 查詢憑證
@enduml
- 支持通過
setAuthenticator()注入自定義實現 - 采用策略模式處理多Realm場景
策略決策層
AuthenticationStrategy控制多Realm協作方式:
| 策略類型 | 行為特征 | 適用場景 |
|---|---|---|
| AtLeastOneSuccess | 任意Realm驗證成功即通過 | 多認證源并聯 |
| FirstSuccessful | 采用首個成功的驗證結果 | 認證源優先級排序 |
| AllSuccessful | 要求全部Realm驗證成功 | 多因素認證 |
數據驗證層
Realm實際執行憑證校驗:
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
String username = (String) token.getPrincipal();
User user = userService.findByUsername(username);
if(user == null) throw new UnknownAccountException();
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
}
- 驗證失敗時拋出具體異常:
IncorrectCredentialsException:密碼錯誤LockedAccountException:賬戶鎖定
- 支持配置多個Realm形成認證鏈
圖片參考:https://blog.csdn.net/qq_45299673/article/details/122091352

校驗過程

流程如下:
授權請求入口層
// 開發者調用方式示例
Subject subject = SecurityUtils.getSubject();
boolean hasAccess = subject.isPermitted("user:delete");
boolean hasRole = subject.hasRole("admin");
核心處理組件
| 組件 | 職責說明 | 默認實現類 |
|---|---|---|
| SecurityManager | 授權請求的中轉調度 | DefaultSecurityManager |
| Authorizer | 授權邏輯的抽象接口 | ModularRealmAuthorizer |
| PermissionResolver | 權限字符串轉換器 | WildcardPermissionResolver |
多Realm處理機制
ModularRealmAuthorizer 的工作邏輯:
-
遍歷所有配置的Realm
-
檢查Realm是否實現
Authorizer接口 -
對符合條件的Realm調用
springboot中shrio認證授權邏輯
導入依賴
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.10.0</version>
</dependency>
執行流程
自定義Realm

public class AccountRealm extends AuthorizingRealm {
/*
* doGetAuthorizationInfo:權限校驗
* 獲取用戶權限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
AccountProfile profile =(AccountProfile) principal.getPrimaryPrincipal();
return info;
}
/*
* doGetAuthenticationInfo:認證校驗
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());
}
}
認證和授權的過程通常需要把這兩個方法實現就可以
認證
通常有兩種寫法,可以對著流程圖

方法一:直接用Jwt種的token進行認證,無需要密碼驗證
*
* doGetAuthenticationInfo:認證校驗
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
JwtToken jwtToken = (JwtToken) authenticationToken;
// 校驗jwt
Claims claim = jwtUtils.getClaimByToken((String) jwtToken.getPrincipal());
if(claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {
throw new UnauthenticatedException("請重新登錄");
}
//獲取到用戶的信息,這里放到claim中,在設計的產生token的過程中
//以下是業務邏輯
String userId = claim.getSubject();
SysUser sysUser = userService.getById(Long.valueOf(userId));
if (sysUser == null) {
throw new UnknownAccountException("賬戶不存在");
}
if (sysUser.getStatus() == -1) {
throw new LockedAccountException("賬戶已被鎖定");
}
AccountProfile profile = new AccountProfile();
BeanUtil.copyProperties(sysUser, profile);
/
return new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());
}
方法一最重要的是通過return new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());進行返回。
參數一為用戶,參數二為token,參數三為realm
方法二:需要進行密碼,以及加鹽的過程
參考:https://blog.csdn.net/hubeilihao/article/details/106414363
public class ShiroRealm extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 第一步從token中取出用戶名
String userName = (String) token.getPrincipal();
// 第二步:根據用戶輸入的userName從數據庫查詢
User user = userService.findByUsername("userName");
if(user==null){
return null;//用戶不存在
}
//第三步 從數據庫取該用戶的passw
String password = user.getPassword();
// 第四步 加鹽
String salt = userCode;
.......其他判斷邏輯......
// 第五步 創建SimpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,password,ByteSource.Util.bytes(salt), this.getName());
//第六步 返回
return simpleAuthenticationInfo;// return的過程完成 password的驗證
}
}
授權邏輯

/*
* doGetAuthorizationInfo:權限校驗
* 獲取用戶權限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
AccountProfile profile =(AccountProfile) principal.getPrimaryPrincipal();
Long userId = profile.getId();
//獲取角色
List<SysRole> roles = sysRoleService.listRolesByUserId(userId);
//獲取菜單
List<SysMenu> menus = sysMenuService.listMenusByUserId(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles.stream().map(SysRole::getCode).collect(Collectors.toSet()));
info.setStringPermissions(menus.stream().map(SysMenu::getPerms).collect(Collectors.toSet()));
return info;
}
PrincipalCollection 是 Shiro 中表示用戶身份信息的集合接口,它:
- 存儲了一個或多個"主體"(Principal)
- 主體代表用戶的身份信息(如用戶對象、用戶名等)
- 通常來自認證階段設置的
AuthenticationInfo
// 獲取主要主體(通常是認證時設置的第一個主體)
Object getPrimaryPrincipal()
// 獲取所有主體(當使用多Realm認證時可能有多個)
Collection<?> getPrincipals()
- 從
PrincipalCollection獲取主要主體 - 強制轉換為自定義的
AccountProfile類型(這是在認證階段存入的用戶概要信息)
最重要的一點是這個,這里都用set集合
//創建 SimpleAuthorizationInfo 對象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//設置角色集合(提取角色編碼)
info.setRoles(roles.stream().map(SysRole::getCode).collect(Collectors.toSet()));
//設置權限字符串集合(提取權限標識)
info.setStringPermissions(menus.stream().map(SysMenu::getPerms).collect(Collectors.toSet()));
- 使用
@RequiresRoles或@RequiresPermissions注解時 - 調用
subject.hasRole()或subject.isPermitted()時 - 首次進行權限檢查時(結果會被緩存)
Shiro配置
@Configuration
public class ShiroConfig {
//自定義Realm永遠完成具體的認證和授權操作
// Realm的父類抽象類
// AuthenticatingRealm 只負責認證(登錄)的Realm父類
// AuthorizingRealm 負責認證(登錄)和授權 的Realm父類
@Bean
public Realm realm() {
return new AccountRealm();
}
/**
* 配置ShiroFilterChainDefinition
* @return
* 定義 URL 路徑與 Shiro 過濾器的映射關系,即哪些路徑需要什么樣的安全控制。
* 默認情況下,Shiro 框架會為所有路徑添加一個 "authc" 過濾器,即要求用戶進行身份驗證。
*/
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
chain.addPathDefinition("/app/**", "anon");
chain.addPathDefinition("/sys/login", "anon");
chain.addPathDefinition("/**", "jwt");
return chain;
}
/**
* 配置ShiroFilterFactoryBean
* @param securityManager
* @param shiroFilterChainDefinition
* @return
* SecurityManager:Shiro 的核心安全管理器(由 Spring 自動注入)
* ShiroFilterChainDefinition:前面定義的 URL 過濾規則(由 Spring 自動注入)
*置一個Shiro的過濾器bean,這個bean將配置Shiro相關的一個規則的攔截
* //例如什么樣的請求可以訪問,什么樣的請求不可以訪問等等
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
ShiroFilterChainDefinition shiroFilterChainDefinition) {
//這是 Shiro 提供的工廠類,用于創建過濾器鏈
// 創建Shiro的攔截的攔截器 ,用于攔截我們的用戶請求
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
//設置Shiro的安全管理,設置管理的同時也會指定某個Realm 用來完成我們權限分配
shiroFilter.setSecurityManager(securityManager);
/**
* shiroFilter.setFilters(MapUtil.of("jwt", new JwtFilter()));
* 使用 Hutool 的 MapUtil.of() 創建了一個單元素 Map
* Key "jwt":過濾器名稱(在路徑規則中引用)
* Value new JwtFilter():自定義的 JWT 過濾器實例
* 這樣就將自定義的 JwtFilter 注冊到了 Shiro 過濾器系統中
*/
shiroFilter.setFilters(MapUtil.of("jwt",new JwtFilter()));
//獲取并設置過濾器鏈映射
//從 shiroFilterChainDefinition 獲取之前定義的路徑-過濾器映射
//(即 /app/**=anon, /sys/login=anon, /**=jwt)
//將這些映射設置到 shiroFilter 中
//定義一個Map集合,這個Map集合中存放的數據全部都是規則,用于設置通知Shiro什么樣的請求可以訪問什么樣的請求不可以訪問
Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
}
() 創建了一個單元素 Map
* Key "jwt":過濾器名稱(在路徑規則中引用)
* Value new JwtFilter():自定義的 JWT 過濾器實例
* 這樣就將自定義的 JwtFilter 注冊到了 Shiro 過濾器系統中
*/
shiroFilter.setFilters(MapUtil.of("jwt",new JwtFilter()));
//獲取并設置過濾器鏈映射
//從 shiroFilterChainDefinition 獲取之前定義的路徑-過濾器映射
//(即 /app/**=anon, /sys/login=anon, /**=jwt)
//將這些映射設置到 shiroFilter 中
//定義一個Map集合,這個Map集合中存放的數據全部都是規則,用于設置通知Shiro什么樣的請求可以訪問什么樣的請求不可以訪問
Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
}

浙公網安備 33010602011771號