Apache Shiro 完整教程
📚 教程目标 :通过理论学习和代码实践,全面掌握Apache Shiro的核心功能、架构设计和最佳实践,能够在实际项目中灵活应用Shiro实现安全认证和授权。
📖 学习路径导航 章节结构
第一章:Shiro概述与基础架构 - 了解Shiro是什么、核心架构和设计理念
第二章:身份认证(Authentication) - 学习用户登录认证的实现
第三章:角色管理(Roles) - 掌握基于角色的授权机制
第四章:权限控制(Permissions) - 深入学习细粒度权限控制
第五章:会话管理(Session Management) - 了解Shiro的会话管理功能
第六章:密码加密(Encryption) - 学习安全的密码加密技术
第七章:自定义Realm - 掌握自定义数据源连接
第八章:缓存支持(Cache) - 了解如何优化Shiro性能
第九章:JWT集成 - 学习现代无状态认证方式
🎯 教程核心价值 通过本教程的学习,你将能够:
✅ 掌握Shiro核心概念 - 理解认证、授权、会话等基础理论 ✅ 熟悉架构设计 - 了解SecurityManager、Subject、Realm等核心组件 ✅ 具备实践能力 - 能够编写完整的Shiro应用 ✅ 解决实际问题 - 应对各种安全场景 ✅ 做出技术选型 - 根据项目需求选择合适的安全方案
第一章:Shiro概述与基础架构 🎯 学习目标
理解Apache Shiro是什么
了解Shiro的发展历史和设计理念
掌握Shiro的核心架构和组件
明确Shiro在现代应用中的定位
理解Shiro的核心优势
🔍 什么是Apache Shiro? Apache Shiro 是一个功能强大且易于使用的Java安全框架,提供了认证、授权、会话管理和加密等功能,用于保护任何类型的应用程序 - 从命令行工具到大型企业级Web应用。
📚 设计理念
“简单即美” - Shiro的设计哲学是让复杂的安全操作变得简单直观
🌟 Shiro核心优势
简洁直观的API设计
最少知识原则:开发者只需要了解必要的API
约定优于配置:合理的默认配置,减少配置负担
分层抽象:不同层次的抽象满足不同复杂度的需求
全面的安全功能覆盖
认证:多因素认证、JWT
授权:基于角色、权限、资源的细粒度控制
会话:支持Web和非Web环境、分布式会话
加密:支持MD5、SHA、AES等主流加密算法
灵活的架构设计
模块化架构:插件式Realm,轻松切换和组合不同的数据源
策略模式:认证、授权策略可配置
事件驱动:支持安全事件的监听和处理
注解支持:基于注解的声明式安全控制
优秀的性能表现
智能缓存:认证和授权结果的智能缓存
懒加载:安全对象的按需加载
连接池:数据库连接的有效管理
最小化锁:并发场景下的性能优化
🏗️ 核心架构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 graph TD subgraph "应用层 - Application Layer" A[Subject 当前用户] end subgraph "核心层 - Core Layer" B[SecurityUtils 安全工具] C[SecurityManager 安全管理器] D[Authenticator 认证器] E[Authorizer 授权器] F[SessionManager 会话管理器] G[CacheManager 缓存管理器] H[Cryptography 加密组件] end subgraph "数据层 - Data Layer" I[AuthenticationStrategy 认证策略] J[CredentialMatcher 凭证匹配器] K[PermissionResolver 权限解析器] L[RolePermissionResolver 角色权限解析器] M[SessionDAO 会话存储] N[SessionFactory 会话工厂] O[SessionValidationScheduler 会话验证调度器] end subgraph "数据源连接层" P[Realm 数据源连接器] Q[JDBC Realm 数据库] R[LDAP Realm 目录服务] S[Custom Realm 自定义数据源] T[Text Realm 文本配置] end A --> B B -.-> C C --> D C --> E C --> F C --> G C --> H D --> I D --> J E --> K E --> L F --> M F --> N F --> O D --> P E --> P P --> Q P --> R P --> S P --> T
🔄 核心组件交互流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 sequenceDiagram participant 应用程序 participant Subject participant SecurityManager participant Authenticator participant Authorizer participant Realm participant 数据源 应用程序->>Subject: 创建认证请求 Subject->>SecurityManager: 委托认证处理 SecurityManager->>Authenticator: 执行认证 Authenticator->>Realm: 查询用户信息 Realm->>数据源: 获取用户凭证 数据源-->>Realm: 返回用户数据 Realm-->>Authenticator: 返回AuthenticationInfo Authenticator-->>SecurityManager: 认证结果 SecurityManager-->>Subject: 认证成功/失败 Subject-->>应用程序: 返回认证状态 应用程序->>Subject: 请求授权检查 Subject->>SecurityManager: 委托授权处理 SecurityManager->>Authorizer: 执行授权检查 Authorizer->>Realm: 查询角色权限 Realm->>数据源: 获取权限数据 数据源-->>Realm: 返回权限数据 Realm-->>Authorizer: 返回AuthorizationInfo Authorizer-->>SecurityManager: 授权结果 SecurityManager-->>Subject: 授权成功/失败 Subject-->>应用程序: 返回授权状态 Note over SecurityManager,SessionManager: 同时管理会话和加密操作
🎭 核心组件详解
组件
角色
核心职责
代码示例
Subject
🎭 当前用户
代表与系统交互的实体
SecurityUtils.getSubject()
SecurityManager
🏰 安全管家
协调所有安全操作
new DefaultSecurityManager()
Realm
🗄️ 数据桥梁
连接安全数据源
new IniRealm("classpath:shiro.ini")
Authenticator
🔍 身份验证官
验证用户身份
ModularRealmAuthenticator
Authorizer
⚖️ 权限审核官
检查用户权限
ModularRealmAuthorizer
SessionManager
📋 会话管理员
管理用户会话
DefaultSessionManager
CacheManager
⚡ 性能加速器
缓存安全数据
MemoryConstrainedCacheManager
🔍 Shiro 与 Spring Security 对比
功能特性
Apache Shiro
Spring Security
Shiro优势
学习曲线
平缓
陡峭
API更简洁直观
配置复杂度
简单
复杂
配置更直观
独立运行
支持
依赖Spring
可独立使用
Web支持
完善
完善
同样强大
非Web支持
优秀
一般
更好的通用性
缓存机制
内置
需集成
缓存更完善
会话管理
强大
一般
会话功能更丰富
加密功能
完善
需集成
加密功能更完整
授权灵活性
高
极高
平衡灵活与简洁
社区活跃度
稳定
活跃
成熟稳定
选择建议 选择Shiro的场景 :
需要独立运行的安全框架
项目不基于Spring框架
需要丰富的会话管理功能
追求简洁的API和配置
需要非Web环境的安全支持
选择Spring Security的场景 :
项目已基于Spring框架
需要与Spring生态深度集成
团队已熟悉Spring技术栈
需要更细粒度的安全控制
🏆 学习成果检验 ✅ 基础理解
第二章:身份认证(Authentication) 🎯 学习目标
理解身份认证的概念
掌握Shiro认证流程
学习如何配置用户账户
掌握异常处理方法
🔐 认证流程详解 认证架构设计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 sequenceDiagram participant 用户 participant 应用程序 participant Subject participant SecurityManager participant Authenticator participant CredentialMatcher participant Realm participant 数据源 用户->>应用程序: 输入用户名密码 应用程序->>Subject: 创建UsernamePasswordToken Subject->>Subject: 调用login(token) Subject->>SecurityManager: 委托login(token) SecurityManager->>Authenticator: 执行认证 Authenticator->>Realm: 遍历配置的Realm Authenticator->>Realm: 检查Realm是否支持此token Realm-->>Authenticator: 支持 Authenticator->>Realm: 执行doGetAuthenticationInfo Realm->>数据源: 查询用户信息 数据源-->>Realm: 返回用户数据(包含加密密码) Realm-->>Authenticator: 返回AuthenticationInfo Authenticator->>CredentialMatcher: 比较凭证 CredentialMatcher-->>Authenticator: 匹配成功 Authenticator-->>SecurityManager: 认证成功 SecurityManager-->>Subject: 设置认证状态 Subject-->>应用程序: 返回认证成功 应用程序-->>用户: 登录成功 rect rgba(0, 0, 0, 0) Note over Authenticator,CredentialMatcher: [密码匹配] end CredentialMatcher-->>Authenticator: 匹配失败 Authenticator-->>SecurityManager: 认证失败 SecurityManager-->>Subject: 抛出IncorrectCredentialsException Subject-->>应用程序: 认证失败 应用程序-->>用户: 密码错误 rect rgba(0, 0, 0, 0) Note over Authenticator,Realm: [Realm支持此token] end Realm-->>Authenticator: 不支持 Authenticator->>Realm: 跳过此Realm,尝试下一个 rect rgba(0, 0, 0, 0) Note over Authenticator,Realm: [Realm不支持此token] end Realm-->>Authenticator: 抛出UnknownAccountException Authenticator-->>SecurityManager: 认证失败 SecurityManager-->>Subject: 抛出异常 Subject-->>应用程序: 认证失败 应用程序-->>用户: 用户名不存在 rect rgba(0, 0, 0, 0) Note over Authenticator,Realm: [用户名不存在] end Realm-->>Authenticator: 抛出LockedAccountException Authenticator-->>SecurityManager: 认证失败 SecurityManager-->>Subject: 抛出异常 Subject-->>应用程序: 认证失败 应用程序-->>用户: 账户已锁定 rect rgba(0, 0, 0, 0) Note over Authenticator,Realm: [账户锁定] end
💡 常见认证异常
异常类型
说明
处理方式
UnknownAccountException
用户名不存在
提示用户注册或检查用户名
IncorrectCredentialsException
密码错误
提示用户重新输入密码
LockedAccountException
账户已锁定
联系管理员解锁账户
DisabledAccountException
账户已禁用
联系管理员启用账户
AuthenticationException
其他认证异常
通用异常处理
💻 代码示例 项目代码文件 :src/main/java/com/shiro/tutorial/SimplifiedAuthExample.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 package com.shiro.tutorial;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.text.IniRealm;import org.apache.shiro.subject.Subject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class SimplifiedAuthExample { private static final Logger LOGGER = LoggerFactory.getLogger(SimplifiedAuthExample.class); public static void main (String[] args) { IniRealm iniRealm = new IniRealm ("classpath:shiro.ini" ); DefaultSecurityManager securityManager = new DefaultSecurityManager (iniRealm); SecurityUtils.setSecurityManager(securityManager); Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken ("user" , "password" ); try { currentUser.login(token); LOGGER.info("用户 {} 登录成功!" , currentUser.getPrincipal()); } catch (UnknownAccountException uae) { LOGGER.error("用户名不存在" , uae); } catch (IncorrectCredentialsException ice) { LOGGER.error("密码错误" , ice); } catch (AuthenticationException ae) { LOGGER.error("认证失败" , ae); } } else { LOGGER.info("用户已认证" ); } } }
🔄 代码执行逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 flowchart TD A[创建IniRealm实例] --> B[加载shiro.ini配置文件] B --> C[创建DefaultSecurityManager实例] C --> D[将IniRealm传入SecurityManager] D --> E[将SecurityManager绑定到SecurityUtils] E --> F[获取当前用户Subject] F --> G{检查用户是否已认证} G -- 是 --> H[记录用户已认证日志] G -- 否 --> I[创建UsernamePasswordToken] I --> J[使用token进行登录认证] J --> K{登录成功?} K -- 是 --> L[记录登录成功日志] K -- 否 --> M{异常类型?} M -- UnknownAccountException --> N[记录用户名不存在日志] M -- IncorrectCredentialsException --> O[记录密码错误日志] M -- 其他 --> P[记录认证失败日志] H --> Q[退出程序] L --> Q N --> Q O --> Q P --> Q
📝 配置文件 项目配置文件 :src/main/resources/shiro.ini
1 2 3 4 [users] user = password
🏆 学习成果检验 ✅ 实践任务
运行 SimplifiedAuthExample 类,观察输出结果
修改用户名或密码,观察异常情况
尝试添加新用户到配置文件
第三章:角色管理(Roles) 🎯 学习目标
理解角色的概念
掌握角色分配方法
学习角色检查机制
了解角色与权限的关系
🧩 角色授权模型 RBAC模型深度解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 erDiagram USER { bigint id PK string username string password string email datetime created_at datetime updated_at } USER_ROLE { bigint user_id FK bigint role_id FK datetime assigned_at "分配时间" } ROLE { bigint id PK string name "角色名称" string description "角色描述" bool enabled "是否启用" datetime created_at } ROLE_PERMISSION { bigint role_id FK bigint permission_id FK } PERMISSION { bigint id PK string name "权限名称" string permission "权限表达式" string resource_type "资源类型" string action "操作类型" } USER ||--o{ USER_ROLE : "分配到" ROLE ||--o{ USER_ROLE : "包含用户" ROLE ||--o{ ROLE_PERMISSION : "拥有权限" PERMISSION ||--o{ ROLE_PERMISSION : "分配给角色"
🎯 角色检查方法
方法
说明
返回值
hasRole(String role)
检查用户是否具有指定角色
boolean
hasRoles(List<String> roles)
检查用户是否具有多个角色
boolean[]
hasAllRoles(Collection<String> roles)
检查用户是否具有所有指定角色
boolean
checkRole(String role)
检查用户是否具有指定角色,失败抛出异常
void
💻 代码示例 项目代码文件 :src/main/java/com/shiro/tutorial/RoleExample.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package com.shiro.tutorial;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.text.IniRealm;import org.apache.shiro.subject.Subject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class RoleExample { private static final Logger LOGGER = LoggerFactory.getLogger(RoleExample.class); public static void main (String[] args) { IniRealm iniRealm = new IniRealm ("classpath:shiro-role.ini" ); DefaultSecurityManager securityManager = new DefaultSecurityManager (iniRealm); SecurityUtils.setSecurityManager(securityManager); Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken ("user" , "password" ); currentUser.login(token); LOGGER.info("用户 {} 登录成功!" , currentUser.getPrincipal()); } if (currentUser.hasRole("role" )){ LOGGER.info("用户 {} 拥有角色 role" , currentUser.getPrincipal()); }else { LOGGER.info("用户 {} 没有角色 role" , currentUser.getPrincipal()); } if (currentUser.hasRole("admin" )){ LOGGER.info("用户 {} 拥有角色 admin" , currentUser.getPrincipal()); }else { LOGGER.info("用户 {} 没有角色 admin" , currentUser.getPrincipal()); } } }
🔄 代码执行逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 flowchart TD A[创建IniRealm实例] --> B[加载shiro-role.ini配置文件] B --> C[创建DefaultSecurityManager实例] C --> D[将IniRealm传入SecurityManager] D --> E[将SecurityManager绑定到SecurityUtils] E --> F[获取当前用户Subject] F --> G{检查用户是否已认证} G -- 是 --> J G -- 否 --> H[创建UsernamePasswordToken] H --> I[使用token进行登录认证] I --> K[记录用户登录成功日志] K --> J[检查用户是否具有'role'角色] J -- 是 --> L[输出'用户拥有角色 role'日志] J -- 否 --> M[输出'用户没有角色 role'日志] L --> N[检查用户是否具有'admin'角色] M --> N N -- 是 --> O[输出'用户拥有角色 admin'日志] N -- 否 --> P[输出'用户没有角色 admin'日志] O --> Q[程序结束] P --> Q
📝 配置文件 项目配置文件 :src/main/resources/shiro-role.ini
1 2 3 4 5 6 7 8 9 10 11 [users] user = password, role[roles] role = permission
🏆 学习成果检验 ✅ 实践任务
运行 RoleExample 类,观察角色检查结果
修改配置文件,为用户添加多个角色
尝试使用 hasRoles() 和 hasAllRoles() 方法检查多个角色
使用 checkRole() 方法检查角色
第四章:权限控制(Permissions) 🎯 学习目标
理解权限的概念和格式
掌握细粒度权限控制
学习权限表达式语法
了解权限检查方法
学习通配符权限
🎮 权限表达式语法详解 权限格式定义 1 2 3 4 5 6 权限格式:资源:操作:实例 示例: - resource:action:object # 允许对指定资源执行操作 - user:* # 允许对用户进行所有操作 - *:read # 允许读取所有资源
权限格式组成
部分
含义
示例
通配符支持
资源
受保护的资源类型
resource, user, order
支持 *
操作
允许的操作类型
action, create, read
支持 *
实例
资源的具体实例
object, 1, user1
支持 *
🎯 权限检查方法
方法
说明
返回值
isPermitted(String permission)
检查用户是否具有指定权限
boolean
isPermitted(List<String> permissions)
检查用户是否具有多个权限
boolean[]
isPermittedAll(String... permissions)
检查用户是否具有所有指定权限
boolean
checkPermission(String permission)
检查用户是否具有指定权限,失败抛出异常
void
💻 代码示例 项目代码文件 :src/main/java/com/shiro/tutorial/PermissionExample.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 package com.shiro.tutorial;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.text.IniRealm;import org.apache.shiro.subject.Subject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class PermissionExample { private static final Logger LOGGER = LoggerFactory.getLogger(PermissionExample.class); public static void main (String[] args) { IniRealm iniRealm = new IniRealm ("classpath:shiro-permission.ini" ); DefaultSecurityManager securityManager = new DefaultSecurityManager (iniRealm); SecurityUtils.setSecurityManager(securityManager); Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken ("user" , "password" ); currentUser.login(token); LOGGER.info("用户 {} 登录成功!" , currentUser.getPrincipal()); } if (currentUser.isPermitted("resource:action:object" )) { LOGGER.info("用户 {} 拥有权限 resource:action:object" , currentUser.getPrincipal()); } else { LOGGER.info("用户 {} 没有权限 resource:action:object" , currentUser.getPrincipal()); } if (currentUser.isPermitted("another:resource:action" )) { LOGGER.info("用户 {} 拥有权限 another:resource:action" , currentUser.getPrincipal()); } else { LOGGER.info("用户 {} 没有权限 another:resource:action" , currentUser.getPrincipal()); } System.exit(0 ); } }
🔄 代码执行逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 flowchart TD subgraph "初始化配置" A[定义原始密码] A --> B[使用MD5算法加密] A --> C[使用SHA-256算法加密] A --> D[使用带盐值的SHA-256加密] B --> E[输出MD5加密结果] C --> F[输出SHA-256加密结果] D --> G[输出带盐值SHA-256加密结果] end subgraph "密码验证流程" H[使用正确密码验证] H --> I{验证成功?} I -- 是 --> J[输出验证成功日志] I -- 否 --> K[输出验证失败日志] L[使用错误密码验证] L --> M{验证成功?} M -- 是 --> N[输出错误密码验证成功日志] M -- 否 --> O[输出错误密码验证失败日志] end J --> P[退出程序] K --> P N --> P O --> P
📝 配置文件 项目配置文件 :src/main/resources/shiro-permission.ini
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [users] user = password, role1, role2[roles] role1 = resource:action:objectrole2 = another:resource:action
🏆 学习成果检验 ✅ 实践任务
运行 PermissionExample 类,观察权限检查结果
修改配置文件,添加更多权限
尝试使用通配符权限
使用 isPermittedAll() 方法检查多个权限
使用 checkPermission() 方法检查权限
第五章:会话管理(Session Management) 🎯 学习目标
理解Shiro会话的概念
掌握会话操作方法
学习会话属性管理
了解会话生命周期
🌐 会话管理概述 Shiro提供了强大的会话管理功能,支持Web和非Web环境,具有以下特点:
跨环境支持:相同的API在Web和非Web环境下均可使用
丰富的会话操作:支持会话属性管理、超时设置等
会话持久化:支持将会话存储到不同的数据源
会话验证:自动验证会话的有效性
💻 代码示例 项目代码文件 :src/main/java/com/shiro/tutorial/SessionExample.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 package com.shiro.tutorial;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.text.IniRealm;import org.apache.shiro.session.Session;import org.apache.shiro.subject.Subject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class SessionExample { private static final Logger LOGGER = LoggerFactory.getLogger(SessionExample.class); public static void main (String[] args) { IniRealm iniRealm = new IniRealm ("classpath:shiro-session.ini" ); DefaultSecurityManager securityManager = new DefaultSecurityManager (iniRealm); SecurityUtils.setSecurityManager(securityManager); Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken ("user" , "password" ); currentUser.login(token); LOGGER.info("用户 {} 登录成功!" , currentUser.getPrincipal()); } Session session = currentUser.getSession(); session.setAttribute("username" , "user" ); session.setAttribute("loginTime" , System.currentTimeMillis()); String username = (String) session.getAttribute("username" ); long loginTime = (Long) session.getAttribute("loginTime" ); LOGGER.info("用户 {} 登录时间:{}" , username, loginTime); session.removeAttribute("loginTime" ); LOGGER.info("用户 {} 登录时间已移除" , username); String sessionId = session.getId().toString(); LOGGER.info("会话ID:{}" , sessionId); long creationTime = session.getStartTimestamp().getTime(); LOGGER.info("会话创建时间:{}" , creationTime); long lastAccessTime = session.getLastAccessTime().getTime(); LOGGER.info("会话最后访问时间:{}" , lastAccessTime); session.setTimeout(3600000 ); LOGGER.info("会话过期时间已设置为1小时" ); long timeout = session.getTimeout(); LOGGER.info("当前会话过期时间:{}毫秒" , timeout); try { session.getAttribute("username" ); LOGGER.info("会话未过期" ); } catch (org.apache.shiro.session.ExpiredSessionException e) { LOGGER.info("会话已过期" ); } session.touch(); LOGGER.info("会话最后访问时间已更新" ); session.stop(); LOGGER.info("会话已失效" ); System.exit(0 ); } }
🔄 代码执行逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 flowchart TD subgraph "初始化配置" A[定义原始密码] A --> B[使用MD5算法加密] A --> C[使用SHA-256算法加密] A --> D[使用带盐值的SHA-256加密] B --> E[输出MD5加密结果] C --> F[输出SHA-256加密结果] D --> G[输出带盐值SHA-256加密结果] end subgraph "密码验证流程" H[使用正确密码验证] H --> I{验证成功?} I -- 是 --> J[输出验证成功日志] I -- 否 --> K[输出验证失败日志] L[使用错误密码验证] L --> M{验证成功?} M -- 是 --> N[输出错误密码验证成功日志] M -- 否 --> O[输出错误密码验证失败日志] end J --> P[退出程序] K --> P N --> P O --> P
📝 配置文件 项目配置文件 :src/main/resources/shiro-session.ini
1 2 3 [users] user = password
🏆 学习成果检验 ✅ 实践任务
运行 SessionExample 类,观察会话操作结果
尝试在会话中存储和获取更多属性
学习会话的生命周期管理
第六章:密码加密(Encryption) 🎯 学习目标
理解密码加密的重要性
掌握Shiro加密API的使用
学习盐值加密技术
了解不同加密算法的特点
🔒 密码加密概述 密码加密是保护用户数据安全的重要手段,Shiro提供了强大的加密支持:
支持多种加密算法:MD5、SHA-1、SHA-256等
内置盐值加密支持
支持多次迭代加密
💻 代码示例 项目代码文件 :src/main/java/com/shiro/tutorial/EncryptionExample.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.shiro.tutorial;import org.apache.shiro.crypto.hash.SimpleHash;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class EncryptionExample { private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionExample.class); public static void main (String[] args) { String originalPassword = "password" ; String md5Hash = new SimpleHash ("MD5" , originalPassword).toHex(); LOGGER.info("原始密码:{},MD5加密结果:{}" , originalPassword, md5Hash); String sha256Hash = new SimpleHash ("SHA-256" , originalPassword).toHex(); LOGGER.info("原始密码:{},SHA-256加密结果:{}" , originalPassword, sha256Hash); String salt = "randomSalt123" ; String saltedHash = new SimpleHash ("SHA-256" , originalPassword, salt, 1024 ).toHex(); LOGGER.info("原始密码:{},盐值加密结果:{}" , originalPassword, saltedHash); String inputPassword = "password" ; String hashedInput = new SimpleHash ("SHA-256" , originalPassword, salt, 1024 ).toHex(); if (hashedInput.equals(saltedHash)) { LOGGER.info("密码验证成功!" ); } else { LOGGER.info("密码验证失败!" ); } String wrongPassword = "wrongPassword" ; String wrongHashedInput = new SimpleHash ("SHA-256" , wrongPassword, salt, 1024 ).toHex(); if (wrongHashedInput.equals(saltedHash)) { LOGGER.info("错误密码验证成功!(不应该出现这种情况)" ); } else { LOGGER.info("错误密码验证失败!(这是预期的结果)" ); } System.exit(0 ); } }
🔄 代码执行逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 flowchart TD subgraph "初始化配置" A[定义原始密码] A --> B[使用MD5算法加密] A --> C[使用SHA-256算法加密] A --> D[使用带盐值的SHA-256加密] B --> E[输出MD5加密结果] C --> F[输出SHA-256加密结果] D --> G[输出带盐值SHA-256加密结果] end subgraph "密码验证流程" H[使用正确密码验证] H --> I{验证成功?} I -- 是 --> J[输出验证成功日志] I -- 否 --> K[输出验证失败日志] L[使用错误密码验证] L --> M{验证成功?} M -- 是 --> N[输出错误密码验证成功日志] M -- 否 --> O[输出错误密码验证失败日志] end J --> P[退出程序] K --> P N --> P O --> P
🏆 学习成果检验 ✅ 实践任务
运行 EncryptionExample 类,观察不同加密算法的结果
尝试使用不同的盐值和迭代次数
理解盐值加密的重要性
第七章:自定义Realm 🎯 学习目标
理解Realm的作用
掌握自定义Realm的开发方法
学习如何实现认证和授权逻辑
了解多Realm配置
🗄️ 自定义Realm概述 Realm是Shiro与数据源之间的桥梁,负责从数据源获取安全数据。自定义Realm允许我们:
从任意数据源获取数据
实现自定义的认证逻辑
实现自定义的授权逻辑
💻 代码示例 项目代码文件 :src/main/java/com/shiro/tutorial/CustomRealmExample.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 package com.shiro.tutorial;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.subject.Subject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.HashMap;import java.util.HashSet;import java.util.Set;public class CustomRealmExample { private static final Logger LOGGER = LoggerFactory.getLogger(CustomRealmExample.class); public static void main (String[] args) { UserRealm userRealm = new UserRealm (); DefaultSecurityManager securityManager = new DefaultSecurityManager (userRealm); SecurityUtils.setSecurityManager(securityManager); Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken ("admin" , "admin123" ); try { currentUser.login(token); LOGGER.info("用户 {} 登录成功!" , currentUser.getPrincipal()); } catch (UnknownAccountException uae) { LOGGER.error("用户名不存在" , uae); } catch (IncorrectCredentialsException ice) { LOGGER.error("密码错误" , ice); } catch (AuthenticationException ae) { LOGGER.error("认证失败" , ae); } } else { LOGGER.info("用户已认证" ); } if (currentUser.hasRole("admin" )) { LOGGER.info("用户具有 admin 角色" ); } else { LOGGER.info("用户不具有 admin 角色" ); } if (currentUser.isPermitted("user:delete" )) { LOGGER.info("用户有删除用户的权限" ); } else { LOGGER.info("用户没有删除用户的权限" ); } if (currentUser.isPermitted("product:create" )) { LOGGER.info("用户有创建产品的权限" ); } else { LOGGER.info("用户没有创建产品的权限" ); } System.exit(0 ); } public static class UserRealm extends AuthorizingRealm { private static final HashMap<String, String> userDatabase = new HashMap <>(); private static final HashMap<String, Set<String>> roleDatabase = new HashMap <>(); private static final HashMap<String, Set<String>> permissionDatabase = new HashMap <>(); static { userDatabase.put("admin" , "admin123" ); userDatabase.put("user" , "user123" ); Set<String> adminRoles = new HashSet <>(); adminRoles.add("admin" ); adminRoles.add("user" ); roleDatabase.put("admin" , adminRoles); Set<String> userRoles = new HashSet <>(); userRoles.add("user" ); roleDatabase.put("user" , userRoles); Set<String> adminPermissions = new HashSet <>(); adminPermissions.add("user:*" ); adminPermissions.add("product:*" ); permissionDatabase.put("admin" , adminPermissions); Set<String> userPermissions = new HashSet <>(); userPermissions.add("product:view" ); userPermissions.add("product:create" ); permissionDatabase.put("user" , userPermissions); } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { String username = (String) authenticationToken.getPrincipal(); String password = userDatabase.get(username); if (password == null ) { throw new UnknownAccountException ("用户不存在" ); } return new SimpleAuthenticationInfo (username, password, getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { String username = (String) principalCollection.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo (); authorizationInfo.addRoles(roleDatabase.get(username)); authorizationInfo.addStringPermissions(permissionDatabase.get(username)); return authorizationInfo; } } }
🔄 代码执行逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 flowchart TD subgraph "初始化配置" A[初始化配置] --> B[创建自定义UserRealm实例] B --> C[创建DefaultSecurityManager实例] C --> D[将UserRealm传入SecurityManager] D --> E[将SecurityManager绑定到SecurityUtils] end subgraph "认证流程" F[认证流程] --> G[获取当前用户Subject] G --> H{检查用户是否已认证} H -- 是 --> J[跳过认证] H -- 否 --> I[创建UsernamePasswordToken] I --> K[执行登录认证] K --> L{登录成功?} L -- 是 --> M[记录登录成功日志] L -- 否 --> N[记录认证失败日志] end subgraph "授权流程" O[授权流程] O --> P[检查用户是否具有admin角色] P -- 是 --> Q[记录角色检查通过日志] P -- 否 --> R[记录角色检查失败日志] Q --> S[检查用户是否具有user:delete权限] R --> S S -- 是 --> T[记录权限检查通过日志] S -- 否 --> U[记录权限检查失败日志] T --> V[检查用户是否具有product:create权限] U --> V V -- 是 --> W[记录权限检查通过日志] V -- 否 --> X[记录权限检查失败日志] end W --> Y[退出程序] X --> Y
🏆 学习成果检验 ✅ 实践任务
运行 CustomRealmExample 类,观察自定义Realm的认证和授权结果
尝试修改模拟数据库中的用户、角色和权限信息
学习如何实现更复杂的自定义Realm
第八章:缓存支持(Cache) 🎯 学习目标
理解Shiro缓存的概念和作用
掌握Shiro缓存管理器的配置
学习如何启用和配置认证缓存
学习如何启用和配置授权缓存
了解不同缓存管理器的特点
📦 缓存概述 Shiro提供了强大的缓存支持,可以显著提高系统性能,减少数据库访问次数。缓存主要应用于以下场景:
认证缓存 :缓存用户的认证信息,避免重复查询数据库
授权缓存 :缓存用户的角色和权限信息,避免重复查询数据库
会话缓存 :缓存用户会话信息,支持分布式会话管理
🔧 项目缓存实现 项目代码文件 :src/main/java/com/shiro/tutorial/CacheExample.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 package com.shiro.tutorial;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.cache.MemoryConstrainedCacheManager;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.text.IniRealm;import org.apache.shiro.subject.Subject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class CacheExample { private static final Logger log = LoggerFactory.getLogger(CacheExample.class); public static void main (String[] args) { IniRealm iniRealm = new IniRealm ("classpath:shiro-session.ini" ); iniRealm.setCacheManager(new MemoryConstrainedCacheManager ()); iniRealm.setAuthenticationCachingEnabled(true ); iniRealm.setAuthorizationCachingEnabled(true ); DefaultSecurityManager securityManager = new DefaultSecurityManager (iniRealm); SecurityUtils.setSecurityManager(securityManager); Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken ("user" , "password" ); try { currentUser.login(token); log.info("用户 {} 登录成功!" , currentUser.getPrincipal()); } catch (Exception e) { log.error("认证失败" , e); } } boolean hasRoleFirst = currentUser.hasRole("admin" ); log.info("第一次检查用户是否有admin角色: {}" , hasRoleFirst); boolean hasRoleSecond = currentUser.hasRole("admin" ); log.info("第二次检查用户是否有admin角色: {}" , hasRoleSecond); boolean isPermittedFirst = currentUser.isPermitted("user:create" ); log.info("第一次检查用户是否有'user:create'权限: {}" , isPermittedFirst); boolean isPermittedSecond = currentUser.isPermitted("user:create" ); log.info("第二次检查用户是否有'user:create'权限: {}" , isPermittedSecond); log.info("=== 缓存说明 ===" ); log.info("1. Shiro提供了多种缓存管理器实现:" ); log.info(" - MemoryConstrainedCacheManager: 基于内存的简单缓存管理器" ); log.info(" - EhCacheManager: 基于Ehcache的缓存管理器" ); log.info(" - SpringCacheManager: 基于Spring Cache的缓存管理器" ); log.info(" - RedisCacheManager: 基于Redis的缓存管理器" ); log.info("" ); log.info("2. 缓存配置要点:" ); log.info(" - 启用认证缓存: setAuthenticationCachingEnabled(true)" ); log.info(" - 启用授权缓存: setAuthorizationCachingEnabled(true)" ); log.info(" - 设置缓存名称: setAuthenticationCacheName() 和 setAuthorizationCacheName()" ); log.info("" ); log.info("3. 缓存的好处:" ); log.info(" - 减少数据库访问次数" ); log.info(" - 提高认证和授权检查的速度" ); log.info(" - 降低系统负载" ); System.exit(0 ); } }
🔄 代码执行逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 flowchart TD subgraph "初始化配置" A[初始化配置] --> B[创建IniRealm实例] B --> C[加载shiro-session.ini配置文件] C --> D[配置MemoryConstrainedCacheManager] D --> E[启用认证缓存] E --> F[启用授权缓存] F --> G[创建DefaultSecurityManager实例] G --> H[将IniRealm传入SecurityManager] H --> I[将SecurityManager绑定到SecurityUtils] end subgraph "认证流程" J[认证流程] --> K[获取当前用户Subject] K --> L{检查用户是否已认证} L -- 是 --> N[跳过认证] L -- 否 --> M[创建UsernamePasswordToken] M --> O[使用token进行登录认证] O --> P{登录成功?} P -- 是 --> Q[记录用户登录成功日志] P -- 否 --> R[记录认证失败日志] end subgraph "缓存演示流程" S[缓存演示流程] --> T[第一次检查admin角色] T --> U[记录第一次角色检查结果] U --> V[第二次检查admin角色] V --> W[记录第二次角色检查结果] W --> X[第一次检查user:create权限] X --> Y[记录第一次权限检查结果] Y --> Z[第二次检查user:create权限] Z --> AA[记录第二次权限检查结果] AA --> AB[输出缓存说明信息] end AB --> AC[退出程序]
📦 缓存工作原理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 flowchart TD subgraph "认证缓存流程" A[用户请求认证] --> B{认证缓存命中?} B -->|是| C[从缓存直接返回认证结果] B -->|否| D[查询数据库获取认证信息] D --> E[将认证信息存储到缓存] E --> F[返回认证结果] end subgraph "授权缓存流程" G[用户请求授权] --> H{授权缓存命中?} H -->|是| I[从缓存直接返回授权结果] H -->|否| J[查询数据库获取授权信息] J --> K[将授权信息存储到缓存] K --> L[返回授权结果] end subgraph "缓存管理器类型" M[缓存管理器] --> N[MemoryConstrainedCacheManager: 内存缓存,适合开发测试] M --> O[EhCacheManager: Ehcache缓存,适合单机环境] M --> P[SpringCacheManager: Spring Cache集成,适合Spring项目] M --> Q[RedisCacheManager: Redis缓存,适合分布式环境] end %% 样式设置 style A fill:#e3f2fd,stroke:#2196f3,stroke-width:2px style G fill:#e8f5e8,stroke:#4caf50,stroke-width:2px style M fill:#fff3e0,stroke:#ff9800,stroke-width:2px
📚 缓存配置要点
选择合适的缓存管理器 :
MemoryConstrainedCacheManager :基于内存的简单缓存管理器,适合开发和测试环境
EhCacheManager :基于Ehcache的缓存管理器,适合单机环境
SpringCacheManager :与Spring Cache集成,适合Spring项目
RedisCacheManager :基于Redis的缓存管理器,适合分布式环境
配置缓存参数 :
设置缓存名称
设置缓存过期时间
设置缓存大小限制
配置缓存刷新策略
缓存清理 :
📝 配置文件 项目配置文件 :src/main/resources/shiro-session.ini
1 2 3 4 5 6 7 8 9 10 11 12 13 [users] user = password, user_role[roles] user_role = product:view, product:createadmin_role = *:*
🏆 学习成果检验 ✅ 实践任务
运行 CacheExample 类,观察缓存功能的效果
尝试使用不同的缓存管理器
修改缓存配置参数,观察不同配置的效果
实现缓存清理功能
学习如何监控缓存使用情况
第九章:JWT集成 🎯 学习目标
理解JWT的概念和优势
掌握Shiro集成JWT的方法
学习JWT令牌的生成和验证
了解无状态认证的实现
掌握JWTRealm的开发
学习JWT工具类的实现
📦 JWT概述 JWT(JSON Web Token)是一种用于在网络应用间传递声明的基于JSON的开放标准:
无状态 :服务器不需要存储会话信息,适合分布式系统
自包含 :令牌中包含了所有必要的用户信息
跨平台 :支持不同语言和平台
安全 :支持签名和加密
可扩展 :可以自定义声明内容
🔍 JWT结构 JWT令牌由三部分组成,用点(.)分隔:
Header :包含令牌类型和签名算法
Payload :包含声明信息(如用户名、过期时间等)
Signature :使用密钥对前两部分进行签名,用于验证令牌的完整性
🏗️ 项目JWT实现架构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 graph TD A[JwtUtils] --> B[生成JWT令牌] A --> C[验证JWT令牌] A --> D[提取用户名] E[JwtToken] --> F[封装JWT令牌] E --> G[实现AuthenticationToken接口] H[JwtRealm] --> I[doGetAuthenticationInfo] H --> J[doGetAuthorizationInfo] J --> K[validateToken] J --> L[getUserRoles] J --> M[getUserPermissions] N[JwtShiroExample] --> O[初始化JWT Realm] O --> P[生成并验证JWT令牌] P --> Q[使用JWT令牌登录] Q --> R[验证角色和权限]
💻 JWT工具类实现 项目代码文件 :src/main/java/com/shiro/tutorial/jwt/JwtUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package com.shiro.tutorial.jwt;import io.jsonwebtoken.Jwts;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.crypto.SecretKey;import java.util.Date;public class JwtUtils { private static final Logger log = LoggerFactory.getLogger(JwtUtils.class); public static final SecretKey SECRET_KEY = Jwts.SIG.HS256.key().build(); private static final long EXPIRATION_TIME = 86400000 ; public static String generateToken (String username) { Date now = new Date (); Date expirationDate = new Date (now.getTime() + EXPIRATION_TIME); return Jwts.builder() .subject(username) .issuedAt(now) .expiration(expirationDate) .signWith(SECRET_KEY) .compact(); } public static String getUsernameFromToken (String token) { try { return Jwts.parser() .verifyWith(SECRET_KEY) .build() .parseSignedClaims(token) .getPayload() .getSubject(); } catch (Exception e) { log.error("解析 JWT 令牌失败" , e); return null ; } } public static boolean validateToken (String token) { try { Jwts.parser() .verifyWith(SECRET_KEY) .build() .parseSignedClaims(token); return true ; } catch (Exception e) { log.error("JWT 令牌验证失败" , e); return false ; } } }
💻 JWT令牌实现 项目代码文件 :src/main/java/com/shiro/tutorial/jwt/JwtToken.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.shiro.tutorial.jwt;import org.apache.shiro.authc.AuthenticationToken;public class JwtToken implements AuthenticationToken { private final String token; public JwtToken (String token) { this .token = token; } @Override public Object getPrincipal () { return token; } @Override public Object getCredentials () { return token; } }
💻 JWT Realm实现 项目代码文件 :src/main/java/com/shiro/tutorial/jwt/JwtRealm.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 package com.shiro.tutorial.jwt;import io.jsonwebtoken.JwtException;import io.jsonwebtoken.Jwts;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 org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.HashSet;import java.util.Set;public class JwtRealm extends AuthorizingRealm { private static final Logger log = LoggerFactory.getLogger(JwtRealm.class); @Override public boolean supports (AuthenticationToken token) { return token instanceof JwtToken; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authToken) throws AuthenticationException { log.info("JWT Realm 开始认证..." ); String token = (String) authToken.getCredentials(); if (!validateToken(token)) { throw new AuthenticationException ("无效的 JWT 令牌" ); } String username = getUsernameFromToken(token); return new SimpleAuthenticationInfo (username, token, getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principals) { log.info("JWT Realm 开始授权..." ); String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo (); Set<String> roles = getUserRoles(username); authorizationInfo.setRoles(roles); Set<String> permissions = getUserPermissions(username); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; } private String getUsernameFromToken (String token) { try { return Jwts.parser() .verifyWith(JwtUtils.SECRET_KEY) .build() .parseSignedClaims(token) .getPayload() .getSubject(); } catch (JwtException e) { log.error("解析 JWT 令牌失败" , e); return null ; } } private boolean validateToken (String token) { try { Jwts.parser() .verifyWith(JwtUtils.SECRET_KEY) .build() .parseSignedClaims(token); return true ; } catch (JwtException e) { log.error("JWT 令牌验证失败" , e); return false ; } } private Set<String> getUserRoles (String username) { Set<String> roles = new HashSet <>(); if ("admin" .equals(username)) { roles.add("admin" ); roles.add("user" ); } else if ("user" .equals(username)) { roles.add("user" ); } return roles; } private Set<String> getUserPermissions (String username) { Set<String> permissions = new HashSet <>(); if ("admin" .equals(username)) { permissions.add("user:*" ); permissions.add("product:*" ); } else if ("user" .equals(username)) { permissions.add("product:view" ); permissions.add("product:create" ); } return permissions; } }
💻 JWT集成示例 项目代码文件 :src/main/java/com/shiro/tutorial/jwt/JwtShiroExample.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 package com.shiro.tutorial.jwt;import org.apache.shiro.SecurityUtils;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.subject.Subject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class JwtShiroExample { private static final Logger log = LoggerFactory.getLogger(JwtShiroExample.class); public static void main (String[] args) { JwtRealm jwtRealm = new JwtRealm (); DefaultSecurityManager securityManager = new DefaultSecurityManager (jwtRealm); SecurityUtils.setSecurityManager(securityManager); Subject currentUser = SecurityUtils.getSubject(); String jwtToken = JwtUtils.generateToken("admin" ); log.info("生成的JWT令牌: {}" , jwtToken); boolean isValid = JwtUtils.validateToken(jwtToken); log.info("JWT令牌是否有效: {}" , isValid); String username = JwtUtils.getUsernameFromToken(jwtToken); log.info("从JWT令牌中提取的用户名: {}" , username); JwtToken token = new JwtToken (jwtToken); try { currentUser.login(token); log.info("用户 {} 使用JWT令牌登录成功!" , currentUser.getPrincipal()); } catch (Exception e) { log.error("JWT 令牌认证失败" , e); } if (currentUser.hasRole("admin" )) { log.info("用户具有 admin 角色" ); } else { log.info("用户不具有 admin 角色" ); } if (currentUser.isPermitted("user:delete" )) { log.info("用户有删除用户的权限" ); } else { log.info("用户没有删除用户的权限" ); } if (currentUser.isPermitted("product:create" )) { log.info("用户有创建产品的权限" ); } else { log.info("用户没有创建产品的权限" ); } System.exit(0 ); } }
🔄 代码执行逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 flowchart TD subgraph "初始化配置" A[初始化配置] --> B[创建JwtRealm实例] B --> C[创建DefaultSecurityManager实例] C --> D[将JwtRealm传入SecurityManager] D --> E[将SecurityManager绑定到SecurityUtils] E --> F[获取当前用户Subject] end subgraph "JWT令牌生成与验证" G[JWT令牌生成与验证] --> H[生成JWT令牌] H --> I[验证JWT令牌有效性] I --> J[从JWT令牌中提取用户名] J --> K[创建JwtToken对象] end subgraph "JWT认证流程" L[JWT认证流程] --> M[使用JwtToken进行登录认证] M --> N{登录成功?} N -- 是 --> O[记录登录成功日志] N -- 否 --> P[记录认证失败日志] end subgraph "JWT授权流程" Q[JWT授权流程] --> R[检查用户是否具有admin角色] R -- 是 --> S[输出用户具有admin角色日志] R -- 否 --> T[输出用户不具有admin角色日志] S --> U[检查用户是否具有user:delete权限] T --> U U -- 是 --> V[输出用户有删除用户的权限日志] U -- 否 --> W[输出用户没有删除用户的权限日志] V --> X[检查用户是否具有product:create权限] W --> X X -- 是 --> Y[输出用户有创建产品的权限日志] X -- 否 --> Z[输出用户没有创建产品的权限日志] end Y --> AA[退出程序] Z --> AA
🏆 学习成果检验 ✅ 实践任务
运行 JwtShiroExample 类,观察JWT集成效果
尝试修改JWT令牌的过期时间,观察令牌过期后的行为
学习如何在Web应用中使用JWT进行身份认证
了解如何实现JWT令牌的刷新机制
学习如何处理JWT令牌的注销操作
📚 总结与进阶 🎯 核心知识点回顾
认证(Authentication) :验证用户身份,确认用户是否为系统合法用户
授权(Authorization) :检查用户权限,确定用户可以访问哪些资源
角色(Role) :权限的集合,用于批量分配权限给用户
权限(Permission) :对资源的访问许可,使用”资源:操作:实例”格式
会话(Session) :管理用户的会话状态,支持Web和非Web环境
加密(Encryption) :保护用户密码安全,支持多种加密算法
Realm :连接Shiro与数据源的桥梁,负责获取安全数据
JWT :无状态认证方式,适合分布式系统
🚀 进阶学习建议
深入学习Shiro源码 :理解Shiro的内部实现机制
集成Spring Boot :学习如何在Spring Boot项目中使用Shiro
分布式会话管理 :学习如何使用Redis等存储会话
多因素认证 :实现更安全的认证方式
权限设计实践 :学习如何设计合理的权限体系
JWT高级应用 :实现令牌刷新、黑名单等功能
性能优化 :学习如何优化Shiro的性能
🎉 恭喜你完成了Apache Shiro完整教程的学习!
通过本教程的学习,你已经掌握了Apache Shiro的核心功能和使用方法,能够在实际项目中灵活应用Shiro实现安全认证和授权。
建议你继续深入学习Shiro的高级特性,并在实际项目中应用所学知识,不断提高自己的安全开发能力。