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,轻松切换和组合不同的数据源
- 策略模式:认证、授权策略可配置
- 事件驱动:支持安全事件的监听和处理
- 注解支持:基于注解的声明式安全控制
优秀的性能表现
- 智能缓存:认证和授权结果的智能缓存
- 懒加载:安全对象的按需加载
- 连接池:数据库连接的有效管理
- 最小化锁:并发场景下的性能优化
🏗️ 核心架构
🔄 核心组件交互流程
🎭 核心组件详解
| 组件 | 角色 | 核心职责 | 代码示例 |
|---|---|---|---|
| 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技术栈
- 需要更细粒度的安全控制
🏆 学习成果检验
✅ 基础理解
- 能够解释Shiro的核心概念
- 能够描述Shiro的三层架构
- 能够说出核心组件的作用
- 能够列举Shiro的核心优势
第二章:身份认证(Authentication)
🎯 学习目标
- 理解身份认证的概念
- 掌握Shiro认证流程
- 学习如何配置用户账户
- 掌握异常处理方法
🔐 认证流程详解
认证架构设计
flowchart TD A[用户提交认证请求] --> B[Subject.login token] B --> C[SecurityManager 接收请求] C --> D[Authenticator 开始认证] D --> E[遍历配置 的Realms] E --> F{Realm支持 此token?} F -->|是| G[Realm执行 doGetAuthenticationInfo] F -->|否| H[跳过此Realm] G --> I{认证 成功?} I -->|是| J[返回 AuthenticationInfo] I -->|否| K[抛出 AuthenticationException] H --> E J --> L[创建 Subject身份] K --> M[认证失败 处理] L --> N[认证成功 登录状态] style A fill:#e1f5fe style N fill:#c8e6c9 style M fill:#ffcdd2 style D fill:#e8f5e8💡 常见认证异常
| 异常类型 | 说明 | 处理方式 |
|---|---|---|
| UnknownAccountException | 用户名不存在 | 提示用户注册或检查用户名 |
| IncorrectCredentialsException | 密码错误 | 提示用户重新输入密码 |
| LockedAccountException | 账户已锁定 | 联系管理员解锁账户 |
| DisabledAccountException | 账户已禁用 | 联系管理员启用账户 |
| AuthenticationException | 其他认证异常 | 通用异常处理 |
💻 代码示例
项目代码文件:src/main/java/com/shiro/tutorial/SimplifiedAuthExample.java
packagecom.shiro.tutorial;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.AuthenticationException;importorg.apache.shiro.authc.IncorrectCredentialsException;importorg.apache.shiro.authc.UnknownAccountException;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.mgt.DefaultSecurityManager;importorg.apache.shiro.realm.text.IniRealm;importorg.apache.shiro.subject.Subject;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/** * Apache Shiro 简单身份认证示例类 * 开发思路: * 1. 首先配置安全管理器和领域(Realm) * 2. 获取当前用户主体(Subject) * 3. 创建身份令牌进行用户认证 * 4. 处理各种认证异常情况 * * @author 李昊哲 李胜龙 * @version 1.0.0 * @since 2025-12-15 */publicclassSimplifiedAuthExample{// 创建日志记录器,用于输出程序运行信息privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(SimplifiedAuthExample.class);/** * 程序入口方法 * 开发过程: * 1. 初始化Shiro安全框架组件 * 2. 进行用户身份认证 * 3. 处理认证结果和异常情况 * * @param args 命令行参数 */publicstaticvoidmain(String[]args){/* 当前示例是如何工作的 1. 在 SimplifiedAuthExample.java 中,认证流程如下: 2. 首先检查用户是否已经认证过(通常在首次运行时返回 false) 3. 如果未认证,提示用户输入用户名和密码 4. 创建 UsernamePasswordToken 对象封装用户输入的凭据 5. 调用 currentUser.login(token) 进行认证 Shiro 在后台使用 IniRealm 读取 shiro.ini 文件中的用户信息,并与用户输入的凭据进行比较 所以,系统是拿着用户输入的账号密码去数据源中验证的。shiro.ini 文件就是我们的"数据源",虽然在实际项目中,我们会使用数据库或 LDAP 等更复杂的存储系统。 */// 创建 IniRealm 实例,指定配置文件位置为classpath下的shiro.ini// IniRealm是Shiro提供的基于INI配置文件的身份认证和授权实现IniRealminiRealm=newIniRealm("classpath:shiro.ini");// 创建 SecurityManager 安全管理器实例,并传入realm// SecurityManager是Shiro框架的核心,负责协调内部安全组件DefaultSecurityManagersecurityManager=newDefaultSecurityManager(iniRealm);// 将 SecurityManager 绑定到 SecurityUtils 工具类// 这样在整个应用程序中都可以通过SecurityUtils获取SecurityManagerSecurityUtils.setSecurityManager(securityManager);// 获取当前执行用户(Subject),Subject代表当前正在与软件进行交互的用户SubjectcurrentUser=SecurityUtils.getSubject();// 判断当前用户是否已经通过认证if(!currentUser.isAuthenticated()){// 如果未认证,则创建UsernamePasswordToken令牌,用于封装用户身份信息// 这里使用预设的用户名"user"和密码"password"UsernamePasswordTokentoken=newUsernamePasswordToken("user","password");try{// 使用令牌进行用户登录认证currentUser.login(token);// 认证成功后,记录用户登录成功的日志信息LOGGER.info("用户 {} 登录成功!",currentUser.getPrincipal());}catch(UnknownAccountExceptionuae){// 捕获用户名不存在异常LOGGER.error("用户名不存在",uae);}catch(IncorrectCredentialsExceptionice){// 捕获密码错误异常LOGGER.error("密码错误",ice);}catch(AuthenticationExceptionae){// 捕获其他身份认证相关异常LOGGER.error("认证失败",ae);}}else{// 如果用户已经通过认证,则记录相应日志LOGGER.info("用户已认证");}}}� 代码执行逻辑
� 配置文件
项目配置文件:src/main/resources/shiro.ini
# 用户配置部分,定义系统中的用户及其密码 [users] # 格式: 用户名 = 密码 user = password🏆 学习成果检验
✅ 实践任务
- 运行
SimplifiedAuthExample类,观察输出结果 - 修改用户名或密码,观察异常情况
- 尝试添加新用户到配置文件
第三章:角色管理(Roles)
🎯 学习目标
- 理解角色的概念
- 掌握角色分配方法
- 学习角色检查机制
- 了解角色与权限的关系
🧩 角色授权模型
RBAC模型深度解析
🎯 角色检查方法
| 方法 | 说明 | 返回值 |
|---|---|---|
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
packagecom.shiro.tutorial;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.mgt.DefaultSecurityManager;importorg.apache.shiro.realm.text.IniRealm;importorg.apache.shiro.subject.Subject;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/** * Apache Shiro 角色权限控制示例类 * 开发思路: * 1. 首先配置安全管理器和领域(Realm) * 2. 获取当前用户主体(Subject) * 3. 创建身份令牌进行用户认证 * 4. 验证用户角色 * * @author 李昊哲 李胜龙 * @version 1.0.0 * @since 2025-12-15 */publicclassRoleExample{// 创建日志记录器,用于输出程序运行信息privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(RoleExample.class);/** * 程序入口方法 * 开发过程: * 1. 初始化Shiro安全框架组件,加载角色配置 * 2. 进行用户身份认证 * 3. 验证用户角色权限检查功能 * * @param args 命令行参数 */publicstaticvoidmain(String[]args){// 创建 IniRealm 实例,使用专门的角色配置文件shiro-role.ini// 该配置文件中包含了用户角色相关信息IniRealminiRealm=newIniRealm("classpath:shiro-role.ini");// 创建 SecurityManager 安全管理器实例,并传入realm// SecurityManager是Shiro框架的核心,负责协调内部安全组件DefaultSecurityManagersecurityManager=newDefaultSecurityManager(iniRealm);// 将 SecurityManager 绑定到 SecurityUtils 工具类// 这样在整个应用程序中都可以通过SecurityUtils获取SecurityManagerSecurityUtils.setSecurityManager(securityManager);// 获取当前执行用户(Subject),Subject代表当前正在与软件进行交互的用户SubjectcurrentUser=SecurityUtils.getSubject();// 判断当前用户是否已经通过认证if(!currentUser.isAuthenticated()){// 如果未认证,则创建UsernamePasswordToken令牌,用于封装用户身份信息// 这里使用预设的用户名"user"和密码"password"UsernamePasswordTokentoken=newUsernamePasswordToken("user","password");// 使用令牌进行用户登录认证currentUser.login(token);// 认证成功后,记录用户登录成功的日志信息LOGGER.info("用户 {} 登录成功!",currentUser.getPrincipal());}// 检查用户是否具有"role"角色(与配置文件中定义的角色一致)// hasRole方法用于判断当前用户是否拥有指定角色if(currentUser.hasRole("role")){LOGGER.info("用户 {} 拥有角色 role",currentUser.getPrincipal());}else{LOGGER.info("用户 {} 没有角色 role",currentUser.getPrincipal());}// 检查用户是否具有"admin"角色(根据配置应该不存在此角色)if(currentUser.hasRole("admin")){LOGGER.info("用户 {} 拥有角色 admin",currentUser.getPrincipal());}else{LOGGER.info("用户 {} 没有角色 admin",currentUser.getPrincipal());}}}🔄 代码执行逻辑
📝 配置文件
项目配置文件:src/main/resources/shiro-role.ini
# 用户配置部分,用于定义系统中的用户、密码及其所属角色 [users] # 格式: 用户名 = 密码, 角色1, 角色2... # 示例配置了一个用户名为"user",密码为"password"的用户,该用户属于"role"角色 user = password, role # 角色权限配置部分,用于定义角色所拥有的权限 [roles] # 格式: 角色名 = 权限 # 示例配置了"role"角色拥有"permission"权限 role = permission🏆 学习成果检验
✅ 实践任务
- 运行
RoleExample类,观察角色检查结果 - 修改配置文件,为用户添加多个角色
- 尝试使用
hasRoles()和hasAllRoles()方法检查多个角色 - 使用
checkRole()方法检查角色
第四章:权限控制(Permissions)
🎯 学习目标
- 理解权限的概念和格式
- 掌握细粒度权限控制
- 学习权限表达式语法
- 了解权限检查方法
- 学习通配符权限
🎮 权限表达式语法详解
权限格式定义
权限格式:资源:操作:实例 示例: - resource:action:object # 允许对指定资源执行操作 - user:* # 允许对用户进行所有操作 - *:read # 允许读取所有资源权限格式组成
| 部分 | 含义 | 示例 | 通配符支持 |
|---|---|---|---|
| 资源 | 受保护的资源类型 | resource, user, order | 支持* |
| 操作 | 允许的操作类型 | action, create, read | 支持* |
| 实例 | 资源的具体实例 | object, 1, user1 | 支持* |
🎯 权限检查方法
| 方法 | 说明 | 返回值 |
|---|---|---|
isPermitted(String permission) | 检查用户是否具有指定权限 | boolean |
isPermittedAll(String... permissions) | 检查用户是否具有所有指定权限 | boolean |
checkPermission(String permission) | 检查用户是否具有指定权限,失败抛出异常 | void |
💻 代码示例
项目代码文件:src/main/java/com/shiro/tutorial/PermissionExample.java
packagecom.shiro.tutorial;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.mgt.DefaultSecurityManager;importorg.apache.shiro.realm.text.IniRealm;importorg.apache.shiro.subject.Subject;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/** * Apache Shiro 权限控制示例类 * 开发思路: * 1. 配置支持权限的Realm和安全管理器 * 2. 实现用户身份认证 * 3. 验证用户细粒度权限控制功能 */publicclassPermissionExample{// 创建日志记录器,用于输出程序运行信息privatestaticfinalLoggerlog=LoggerFactory.getLogger(PermissionExample.class);/** * 程序入口方法 * 开发过程: * 1. 初始化Shiro安全框架组件,加载权限配置 * 2. 进行用户身份认证 * 3. 验证用户权限检查功能 */publicstaticvoidmain(String[]args){// 创建 IniRealm 实例,使用专门的权限配置文件shiro-permission.iniIniRealminiRealm=newIniRealm("classpath:shiro-permission.ini");// 创建 SecurityManager 安全管理器实例,并传入realmDefaultSecurityManagersecurityManager=newDefaultSecurityManager(iniRealm);// 将 SecurityManager 绑定到 SecurityUtils 工具类SecurityUtils.setSecurityManager(securityManager);// 获取当前执行用户(Subject)SubjectcurrentUser=SecurityUtils.getSubject();// 判断当前用户是否已经通过认证if(!currentUser.isAuthenticated()){// 如果未认证,则创建UsernamePasswordToken令牌UsernamePasswordTokentoken=newUsernamePasswordToken("user","password");// 执行用户登录认证currentUser.login(token);// 认证成功后,记录用户登录成功的日志信息log.info("用户 {} 登录成功!",currentUser.getPrincipal());}// 检查用户是否具有"resource:action:object"权限if(currentUser.isPermitted("resource:action:object")){log.info("用户有权访问 resource:action:object");}else{log.info("用户无权访问 resource:action:object");}// 检查用户是否具有"another:resource:action"权限if(currentUser.isPermitted("another:resource:action")){log.info("用户有权访问 another:resource:action");}else{log.info("用户无权访问 another:resource:action");}// 正常退出程序System.exit(0);}}� 代码执行逻辑
�� 配置文件
项目配置文件:src/main/resources/shiro-permission.ini
# 用户配置,格式: 用户名 = 密码, 角色1, 角色2... [users] # 用户user具有role1和role2两个角色 user = password, role1, role2 # 用户admin具有admin角色 admin = admin123, admin # 角色权限配置,格式: 角色名 = 权限1, 权限2... [roles] # role1角色具有对resource的action权限,作用对象为object role1 = resource:action:object # role2角色具有对another的resource权限,作用对象为action role2 = another:resource:action # admin角色具有所有权限 admin = *🏆 学习成果检验
✅ 实践任务
- 运行
PermissionExample类,观察权限检查结果 - 修改配置文件,添加更多权限
- 尝试使用通配符权限
- 使用
isPermittedAll()方法检查多个权限 - 使用
checkPermission()方法检查权限
第五章:会话管理(Session Management)
🎯 学习目标
- 理解Shiro会话的概念
- 掌握会话操作方法
- 学习会话属性管理
- 了解会话生命周期
🌐 会话管理概述
Shiro提供了强大的会话管理功能,支持Web和非Web环境,具有以下特点:
- 跨环境支持:相同的API在Web和非Web环境下均可使用
- 丰富的会话操作:支持会话属性管理、超时设置等
- 会话持久化:支持将会话存储到不同的数据源
- 会话验证:自动验证会话的有效性
💻 代码示例
项目代码文件:src/main/java/com/shiro/tutorial/SessionExample.java
packagecom.shiro.tutorial;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.mgt.DefaultSecurityManager;importorg.apache.shiro.realm.text.IniRealm;importorg.apache.shiro.session.Session;importorg.apache.shiro.subject.Subject;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;publicclassSessionExample{privatestaticfinalLoggerlog=LoggerFactory.getLogger(SessionExample.class);publicstaticvoidmain(String[]args){// 创建 IniRealm 实例,使用专门的会话配置文件shiro-session.iniIniRealminiRealm=newIniRealm("classpath:shiro-session.ini");DefaultSecurityManagersecurityManager=newDefaultSecurityManager(iniRealm);SecurityUtils.setSecurityManager(securityManager);SubjectcurrentUser=SecurityUtils.getSubject();// 用户认证if(!currentUser.isAuthenticated()){// 注意:这里使用的是shiro-session.ini中配置的用户user1/password1UsernamePasswordTokentoken=newUsernamePasswordToken("user1","password1");currentUser.login(token);log.info("用户 {} 登录成功!",currentUser.getPrincipal());}// 获取当前用户的会话(Session)对象Sessionsession=currentUser.getSession();// 在会话中设置属性,存储用户相关信息session.setAttribute("username","user1");session.setAttribute("loginTime",System.currentTimeMillis());// 从会话中获取之前设置的属性值Stringusername=(String)session.getAttribute("username");LongloginTime=(Long)session.getAttribute("loginTime");// 输出从会话中获取的信息到日志log.info("从会话中获取用户名: {}",username);log.info("登录时间: {}",loginTime);// 从会话中移除指定属性session.removeAttribute("loginTime");log.info("loginTime 属性已被移除");System.exit(0);}}� 代码执行逻辑
� 配置文件
项目配置文件:src/main/resources/shiro-session.ini
# 用户配置,格式: 用户名 = 密码 [users] user1 = password1🏆 学习成果检验
✅ 实践任务
- 运行
SessionExample类,观察会话操作结果 - 尝试在会话中存储和获取更多属性
- 学习会话的生命周期管理
第六章:密码加密(Encryption)
🎯 学习目标
- 理解密码加密的重要性
- 掌握Shiro加密API的使用
- 学习盐值加密技术
- 了解不同加密算法的特点
🔒 密码加密概述
密码加密是保护用户数据安全的重要手段,Shiro提供了强大的加密支持:
- 支持多种加密算法:MD5、SHA-1、SHA-256等
- 内置盐值加密支持
- 支持多次迭代加密
💻 代码示例
项目代码文件:src/main/java/com/shiro/tutorial/EncryptionExample.java
packagecom.shiro.tutorial;importorg.apache.shiro.crypto.hash.SimpleHash;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;publicclassEncryptionExample{privatestaticfinalLoggerlog=LoggerFactory.getLogger(EncryptionExample.class);publicstaticvoidmain(String[]args){// 原始密码StringoriginalPassword="password";// 使用MD5算法加密密码Stringmd5Hash=newSimpleHash("MD5",originalPassword).toHex();log.info("原始密码: {}",originalPassword);log.info("MD5加密后: {}",md5Hash);// 使用SHA-256算法加密密码Stringsha256Hash=newSimpleHash("SHA-256",originalPassword).toHex();log.info("SHA-256加密后: {}",sha256Hash);// 使用盐值加密密码(增强安全性)Stringsalt="randomSalt123";StringsaltedHash=newSimpleHash("SHA-256",originalPassword,salt,1024).toHex();log.info("带盐值SHA-256加密后: {}",saltedHash);// 演示密码验证过程StringinputPassword="password";StringhashedInput=newSimpleHash("SHA-256",inputPassword,salt,1024).toHex();if(hashedInput.equals(saltedHash)){log.info("密码验证成功!");}else{log.info("密码验证失败!");}System.exit(0);}}🔄 代码执行逻辑
🏆 学习成果检验
✅ 实践任务
- 运行
EncryptionExample类,观察不同加密算法的结果 - 尝试使用不同的盐值和迭代次数
- 理解盐值加密的重要性
第七章:自定义Realm
🎯 学习目标
- 理解Realm的作用
- 掌握自定义Realm的开发方法
- 学习如何实现认证和授权逻辑
- 了解多Realm配置
🗄️ 自定义Realm概述
Realm是Shiro与数据源之间的桥梁,负责从数据源获取安全数据。自定义Realm允许我们:
- 从任意数据源获取数据
- 实现自定义的认证逻辑
- 实现自定义的授权逻辑
💻 代码示例
项目代码文件:src/main/java/com/shiro/tutorial/CustomRealmExample.java
packagecom.shiro.tutorial;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.*;importorg.apache.shiro.authz.AuthorizationInfo;importorg.apache.shiro.authz.SimpleAuthorizationInfo;importorg.apache.shiro.mgt.DefaultSecurityManager;importorg.apache.shiro.realm.AuthorizingRealm;importorg.apache.shiro.subject.PrincipalCollection;importorg.apache.shiro.subject.Subject;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.util.HashMap;importjava.util.HashSet;importjava.util.Set;publicclassCustomRealmExample{privatestaticfinalLoggerlog=LoggerFactory.getLogger(CustomRealmExample.class);publicstaticvoidmain(String[]args){// 创建自定义 Realm 实例UserRealmuserRealm=newUserRealm();// 创建 SecurityManager 安全管理器实例,并传入自定义realmDefaultSecurityManagersecurityManager=newDefaultSecurityManager(userRealm);// 将 SecurityManager 绑定到 SecurityUtils 工具类SecurityUtils.setSecurityManager(securityManager);// 获取当前执行用户(Subject)SubjectcurrentUser=SecurityUtils.getSubject();// 判断当前用户是否已经通过认证if(!currentUser.isAuthenticated()){// 如果未认证,则创建UsernamePasswordToken令牌UsernamePasswordTokentoken=newUsernamePasswordToken("admin","admin123");try{// 使用令牌进行用户登录认证currentUser.login(token);// 认证成功后,记录用户登录成功的日志信息log.info("用户 {} 登录成功!",currentUser.getPrincipal());}catch(UnknownAccountExceptionuae){log.error("用户名不存在",uae);}catch(IncorrectCredentialsExceptionice){log.error("密码错误",ice);}catch(AuthenticationExceptionae){log.error("认证失败",ae);}}else{// 如果用户已经通过认证,则记录相应日志log.info("用户已认证");}// 验证用户是否有 admin 角色if(currentUser.hasRole("admin")){log.info("用户具有 admin 角色");}else{log.info("用户不具有 admin 角色");}// 验证用户是否有user:delete权限if(currentUser.isPermitted("user:delete")){log.info("用户有删除用户的权限");}else{log.info("用户没有删除用户的权限");}// 验证用户是否有product:create权限if(currentUser.isPermitted("product:create")){log.info("用户有创建产品的权限");}else{log.info("用户没有创建产品的权限");}System.exit(0);}/** * 自定义Realm类,用于处理用户认证和授权 */publicstaticclassUserRealmextendsAuthorizingRealm{// 模拟用户数据库privatestaticfinalHashMap<String,String>userDatabase=newHashMap<>();// 模拟角色数据库privatestaticfinalHashMap<String,Set<String>>roleDatabase=newHashMap<>();// 模拟权限数据库privatestaticfinalHashMap<String,Set<String>>permissionDatabase=newHashMap<>();// 初始化模拟数据static{// 添加用户(用户名:密码)userDatabase.put("admin","admin123");userDatabase.put("user","user123");// 添加用户角色Set<String>adminRoles=newHashSet<>();adminRoles.add("admin");adminRoles.add("user");roleDatabase.put("admin",adminRoles);Set<String>userRoles=newHashSet<>();userRoles.add("user");roleDatabase.put("user",userRoles);// 添加角色权限Set<String>adminPermissions=newHashSet<>();adminPermissions.add("user:*");adminPermissions.add("product:*");permissionDatabase.put("admin",adminPermissions);Set<String>userPermissions=newHashSet<>();userPermissions.add("product:view");userPermissions.add("product:create");permissionDatabase.put("user",userPermissions);}/** * 认证方法:获取认证信息 */@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthToken)throwsAuthenticationException{// 获取用户名Stringusername=(String)authToken.getPrincipal();// 从模拟数据库中获取用户密码Stringpassword=userDatabase.get(username);// 检查用户是否存在if(password==null){thrownewUnknownAccountException("用户不存在");}// 返回认证信息returnnewSimpleAuthenticationInfo(username,password,getName());}/** * 授权方法:获取授权信息 */@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){// 获取用户名Stringusername=(String)principals.getPrimaryPrincipal();// 创建授权信息对象SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();// 添加角色Set<String>roles=roleDatabase.get(username);if(roles!=null){authorizationInfo.setRoles(roles);}// 添加权限Set<String>permissions=permissionDatabase.get(username);if(permissions!=null){authorizationInfo.setStringPermissions(permissions);}returnauthorizationInfo;}}}🔄 代码执行逻辑
🏆 学习成果检验
✅ 实践任务
- 运行
CustomRealmExample类,观察自定义Realm的认证和授权结果 - 尝试修改模拟数据库中的用户、角色和权限信息
- 学习如何实现更复杂的自定义Realm
第八章:缓存支持(Cache)
🎯 学习目标
- 理解Shiro缓存的概念和作用
- 掌握Shiro缓存管理器的配置
- 学习如何启用和配置认证缓存
- 学习如何启用和配置授权缓存
- 了解不同缓存管理器的特点
📦 缓存概述
Shiro提供了强大的缓存支持,可以显著提高系统性能,减少数据库访问次数。缓存主要应用于以下场景:
- 认证缓存:缓存用户的认证信息,避免重复查询数据库
- 授权缓存:缓存用户的角色和权限信息,避免重复查询数据库
- 会话缓存:缓存用户会话信息,支持分布式会话管理
🔧 项目缓存实现
项目代码文件:src/main/java/com/shiro/tutorial/CacheExample.java
packagecom.shiro.tutorial;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.cache.MemoryConstrainedCacheManager;importorg.apache.shiro.mgt.DefaultSecurityManager;importorg.apache.shiro.realm.text.IniRealm;importorg.apache.shiro.subject.Subject;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/** * Apache Shiro 缓存支持示例类 * 开发思路: * 1. 配置支持缓存的Realm和安全管理器 * 2. 实现用户身份认证 * 3. 演示Shiro缓存功能的使用 */publicclassCacheExample{// 创建日志记录器,用于输出程序运行信息privatestaticfinalLoggerlog=LoggerFactory.getLogger(CacheExample.class);/** * 程序入口方法 * 开发过程: * 1. 初始化Shiro安全框架组件,配置缓存管理器 * 2. 进行用户身份认证 * 3. 演示缓存功能 */publicstaticvoidmain(String[]args){// 创建 IniRealm 实例,使用专门的会话配置文件shiro-session.iniIniRealminiRealm=newIniRealm("classpath:shiro-session.ini");// 启用缓存 - 设置缓存管理器iniRealm.setCacheManager(newMemoryConstrainedCacheManager());// 启用认证缓存iniRealm.setAuthenticationCachingEnabled(true);// 启用授权缓存iniRealm.setAuthorizationCachingEnabled(true);// 创建 SecurityManager 安全管理器实例,并传入realmDefaultSecurityManagersecurityManager=newDefaultSecurityManager(iniRealm);// 将 SecurityManager 绑定到 SecurityUtils 工具类SecurityUtils.setSecurityManager(securityManager);// 获取当前执行用户(Subject)SubjectcurrentUser=SecurityUtils.getSubject();// 判断当前用户是否已经通过认证if(!currentUser.isAuthenticated()){// 如果未认证,则创建UsernamePasswordToken令牌UsernamePasswordTokentoken=newUsernamePasswordToken("user1","password1");try{// 执行用户登录认证currentUser.login(token);// 认证成功后,记录用户登录成功的日志信息log.info("用户 {} 登录成功!",currentUser.getPrincipal());}catch(Exceptione){log.error("认证失败",e);}}// 检查用户角色(第一次)booleanhasRoleFirst=currentUser.hasRole("admin");log.info("第一次检查用户是否有admin角色: {}",hasRoleFirst);// 再次检查用户角色(应该从缓存中获取)booleanhasRoleSecond=currentUser.hasRole("admin");log.info("第二次检查用户是否有admin角色: {}",hasRoleSecond);// 检查用户权限(第一次)booleanisPermittedFirst=currentUser.isPermitted("user:create");log.info("第一次检查用户是否有'user:create'权限: {}",isPermittedFirst);// 再次检查用户权限(应该从缓存中获取)booleanisPermittedSecond=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);}}� 代码执行逻辑
� 缓存工作原理
📚 缓存配置要点
选择合适的缓存管理器:
- MemoryConstrainedCacheManager:基于内存的简单缓存管理器,适合开发和测试环境
- EhCacheManager:基于Ehcache的缓存管理器,适合单机环境
- SpringCacheManager:与Spring Cache集成,适合Spring项目
- RedisCacheManager:基于Redis的缓存管理器,适合分布式环境
配置缓存参数:
- 设置缓存名称
- 设置缓存过期时间
- 设置缓存大小限制
- 配置缓存刷新策略
缓存清理:
- 手动清理缓存
- 配置自动清理策略
- 监听缓存事件
🏆 学习成果检验
✅ 实践任务
- 运行
CacheExample类,观察缓存功能的效果 - 尝试使用不同的缓存管理器
- 修改缓存配置参数,观察不同配置的效果
- 实现缓存清理功能
- 学习如何监控缓存使用情况
第九章:JWT集成
🎯 学习目标
- 理解JWT的概念和优势
- 掌握Shiro集成JWT的方法
- 学习JWT令牌的生成和验证
- 了解无状态认证的实现
- 掌握JWTRealm的开发
- 学习JWT工具类的实现
📦 JWT概述
JWT(JSON Web Token)是一种用于在网络应用间传递声明的基于JSON的开放标准:
- 无状态:服务器不需要存储会话信息,适合分布式系统
- 自包含:令牌中包含了所有必要的用户信息
- 跨平台:支持不同语言和平台
- 安全:支持签名和加密
- 可扩展:可以自定义声明内容
� JWT结构
JWT令牌由三部分组成,用点(.)分隔:
- Header:包含令牌类型和签名算法
- Payload:包含声明信息(如用户名、过期时间等)
- Signature:使用密钥对前两部分进行签名,用于验证令牌的完整性
🏗️ 项目JWT实现架构
�� JWT工具类实现
项目代码文件:src/main/java/com/shiro/tutorial/jwt/JwtUtils.java
packagecom.shiro.tutorial.jwt;importio.jsonwebtoken.Jwts;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjavax.crypto.SecretKey;importjava.util.Date;/** * JWT工具类 * 用于生成和验证JWT令牌 */publicclassJwtUtils{// 创建日志记录器,用于输出程序运行信息privatestaticfinalLoggerlog=LoggerFactory.getLogger(JwtUtils.class);// JWT 签名密钥publicstaticfinalSecretKeySECRET_KEY=Jwts.SIG.HS256.key().build();// 令牌过期时间(24小时)privatestaticfinallongEXPIRATION_TIME=86400000;/** * 生成 JWT 令牌 * @param username 用户名 * @return JWT 令牌字符串 */publicstaticStringgenerateToken(Stringusername){Datenow=newDate();DateexpirationDate=newDate(now.getTime()+EXPIRATION_TIME);returnJwts.builder().subject(username).issuedAt(now).expiration(expirationDate).signWith(SECRET_KEY).compact();}/** * 从 JWT 令牌中提取用户名 * @param token JWT 令牌 * @return 用户名 */publicstaticStringgetUsernameFromToken(Stringtoken){try{returnJwts.parser().verifyWith(SECRET_KEY).build().parseSignedClaims(token).getPayload().getSubject();}catch(Exceptione){log.error("解析 JWT 令牌失败",e);returnnull;}}/** * 验证 JWT 令牌的有效性 * @param token JWT 令牌 * @return 令牌是否有效 */publicstaticbooleanvalidateToken(Stringtoken){try{Jwts.parser().verifyWith(SECRET_KEY).build().parseSignedClaims(token);returntrue;}catch(Exceptione){log.error("JWT 令牌验证失败",e);returnfalse;}}}💻 JWT令牌实现
项目代码文件:src/main/java/com/shiro/tutorial/jwt/JwtToken.java
packagecom.shiro.tutorial.jwt;importorg.apache.shiro.authc.AuthenticationToken;/** * JWT令牌类 * 用于封装JWT令牌信息,实现Shiro的AuthenticationToken接口 */publicclassJwtTokenimplementsAuthenticationToken{/** * JWT 令牌字符串 */privatefinalStringtoken;/** * 构造函数 * @param token JWT 令牌字符串 */publicJwtToken(Stringtoken){this.token=token;}/** * 获取用户身份信息 * @return JWT 令牌字符串 */@OverridepublicObjectgetPrincipal(){returntoken;}/** * 获取用户凭据信息 * @return JWT 令牌字符串 */@OverridepublicObjectgetCredentials(){returntoken;}}💻 JWT Realm实现
项目代码文件:src/main/java/com/shiro/tutorial/jwt/JwtRealm.java
packagecom.shiro.tutorial.jwt;importio.jsonwebtoken.*;importorg.apache.shiro.authc.AuthenticationException;importorg.apache.shiro.authc.AuthenticationInfo;importorg.apache.shiro.authc.AuthenticationToken;importorg.apache.shiro.authc.SimpleAuthenticationInfo;importorg.apache.shiro.authz.AuthorizationInfo;importorg.apache.shiro.authz.SimpleAuthorizationInfo;importorg.apache.shiro.realm.AuthorizingRealm;importorg.apache.shiro.subject.PrincipalCollection;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.util.HashSet;importjava.util.Set;/** * JWT Realm类 * 用于处理JWT令牌的认证和授权 */publicclassJwtRealmextendsAuthorizingRealm{// 创建日志记录器,用于输出程序运行信息privatestaticfinalLoggerlog=LoggerFactory.getLogger(JwtRealm.class);/** * 设置 Realm 支持的AuthenticationToken类型 * @param token 认证令牌 * @return 是否支持该令牌类型 */@Overridepublicbooleansupports(AuthenticationTokentoken){returntokeninstanceofJwtToken;}/** * 认证方法:获取认证信息 * @param authToken 认证令牌 * @return 认证信息 * @throws AuthenticationException 认证异常 */@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthToken)throwsAuthenticationException{log.info("JWT Realm 开始认证...");// 获取 JWT 令牌Stringtoken=(String)authToken.getCredentials();// 验证 JWT 令牌if(!validateToken(token)){thrownewAuthenticationException("无效的 JWT 令牌");}// 从 JWT 令牌中获取用户名Stringusername=getUsernameFromToken(token);// 返回认证信息returnnewSimpleAuthenticationInfo(username,token,getName());}/** * 授权方法:获取授权信息 * @param principals 身份信息 * @return 授权信息 */@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){log.info("JWT Realm 开始授权...");// 获取用户名Stringusername=(String)principals.getPrimaryPrincipal();// 创建授权信息对象SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();// 添加角色(模拟数据)Set<String>roles=getUserRoles(username);authorizationInfo.setRoles(roles);// 添加权限(模拟数据)Set<String>permissions=getUserPermissions(username);authorizationInfo.setStringPermissions(permissions);returnauthorizationInfo;}/** * 从 JWT 令牌中提取用户名 * @param token JWT 令牌 * @return 用户名 */privateStringgetUsernameFromToken(Stringtoken){try{returnJwts.parser().verifyWith(JwtUtils.SECRET_KEY).build().parseSignedClaims(token).getPayload().getSubject();}catch(JwtExceptione){log.error("解析 JWT 令牌失败",e);returnnull;}}/** * 验证 JWT 令牌的有效性 * @param token JWT 令牌 * @return 令牌是否有效 */privatebooleanvalidateToken(Stringtoken){try{Jwts.parser().verifyWith(JwtUtils.SECRET_KEY).build().parseSignedClaims(token);returntrue;}catch(JwtExceptione){log.error("JWT 令牌验证失败",e);returnfalse;}}/** * 获取用户角色(模拟实现) * @param username 用户名 * @return 用户角色集合 */privateSet<String>getUserRoles(Stringusername){Set<String>roles=newHashSet<>();// 模拟用户角色数据if("admin".equals(username)){roles.add("admin");roles.add("user");}elseif("user".equals(username)){roles.add("user");}returnroles;}/** * 获取用户权限(模拟实现) * @param username 用户名 * @return 用户权限集合 */privateSet<String>getUserPermissions(Stringusername){Set<String>permissions=newHashSet<>();// 模拟用户权限数据if("admin".equals(username)){permissions.add("user:*");permissions.add("product:*");}elseif("user".equals(username)){permissions.add("product:view");permissions.add("product:create");}returnpermissions;}}💻 JWT集成示例
项目代码文件:src/main/java/com/shiro/tutorial/jwt/JwtShiroExample.java
packagecom.shiro.tutorial.jwt;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.mgt.DefaultSecurityManager;importorg.apache.shiro.subject.Subject;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/** * Apache Shiro JWT集成示例类 * 开发思路: * 1. 创建JWT工具类用于生成和验证JWT令牌 * 2. 创建JWT Realm处理JWT认证和授权 * 3. 配置安全管理器使用JWT Realm * 4. 演示JWT令牌的生成和验证过程 */publicclassJwtShiroExample{// 创建日志记录器,用于输出程序运行信息privatestaticfinalLoggerlog=LoggerFactory.getLogger(JwtShiroExample.class);/** * 程序入口方法 * 开发过程: * 1. 初始化Shiro安全框架组件,使用JWT Realm * 2. 生成JWT令牌 * 3. 使用JWT令牌进行用户身份认证和授权验证 */publicstaticvoidmain(String[]args){// 创建JWT Realm实例JwtRealmjwtRealm=newJwtRealm();// 创建 SecurityManager 安全管理器实例,并传入JWT RealmDefaultSecurityManagersecurityManager=newDefaultSecurityManager(jwtRealm);// 将 SecurityManager 绑定到 SecurityUtils 工具类SecurityUtils.setSecurityManager(securityManager);// 获取当前执行用户(Subject)SubjectcurrentUser=SecurityUtils.getSubject();// 生成JWT令牌(模拟用户登录成功后生成令牌)StringjwtToken=JwtUtils.generateToken("admin");log.info("生成的JWT令牌: {}",jwtToken);// 验证 JWT 令牌booleanisValid=JwtUtils.validateToken(jwtToken);log.info("JWT令牌是否有效: {}",isValid);// 从 JWT 令牌中提取用户名Stringusername=JwtUtils.getUsernameFromToken(jwtToken);log.info("从JWT令牌中提取的用户名: {}",username);// 使用 JWT 令牌进行认证JwtTokentoken=newJwtToken(jwtToken);try{// 使用 JWT 令牌进行用户登录认证currentUser.login(token);// 认证成功后,记录用户登录成功的日志信息log.info("用户 {} 使用JWT令牌登录成功!",currentUser.getPrincipal());}catch(Exceptione){log.error("JWT 令牌认证失败",e);}// 验证用户是否有 admin 角色if(currentUser.hasRole("admin")){log.info("用户具有 admin 角色");}else{log.info("用户不具有 admin 角色");}// 验证用户是否有user:delete权限if(currentUser.isPermitted("user:delete")){log.info("用户有删除用户的权限");}else{log.info("用户没有删除用户的权限");}System.exit(0);}}🔄 代码执行逻辑
🔐 JWT安全最佳实践
- 使用强密钥:选择足够长且复杂的密钥,定期轮换
- 合理设置过期时间:根据业务需求设置适当的过期时间
- 使用HTTPS传输:确保JWT令牌在传输过程中不被窃取
- 不要存储敏感信息:令牌中只存储必要的非敏感信息
- 实现令牌刷新机制:支持令牌过期前的自动刷新
- 添加黑名单机制:支持主动吊销令牌
- 使用合适的签名算法:根据安全需求选择合适的签名算法
🏆 学习成果检验
✅ 实践任务
- 运行
JwtShiroExample类,观察JWT集成的结果 - 学习JWT令牌的生成和验证过程
- 理解无状态认证的优势和实现方法
- 尝试修改JWT过期时间,观察不同过期时间的效果
- 尝试添加新的角色和权限到JwtRealm中
- 实现JWT令牌的刷新机制
🌟 扩展学习
- JWT与传统Session的对比:理解两种认证方式的优缺点
- JWT在分布式系统中的应用:学习如何在微服务架构中使用JWT
- JWT加密:了解如何对JWT令牌进行加密,保护敏感信息
- JWT扩展声明:学习如何自定义JWT声明,添加额外的用户信息
- JWT性能优化:了解如何优化JWT的生成和验证性能
🎓 总结与进阶
📋 核心知识点回顾
- 认证(Authentication):验证用户身份,确认用户是否为系统合法用户
- 授权(Authorization):检查用户权限,确定用户可以访问哪些资源
- 角色(Role):权限的集合,用于批量分配权限给用户
- 权限(Permission):对资源的访问许可,采用"资源:操作:实例"格式
- 会话(Session):管理用户的会话状态,支持跨环境使用
- 加密(Encryption):保护用户密码安全,支持多种加密算法
- Realm:连接Shiro与数据源的桥梁,负责获取安全数据
- JWT:无状态认证方式,适合分布式系统
🚀 进阶学习建议
- 深入学习Shiro源码:理解Shiro的内部实现机制
- 集成Spring Boot:学习如何在Spring Boot项目中使用Shiro
- 分布式会话管理:学习如何使用Redis等存储会话
- 多因素认证:实现更安全的认证方式
- 权限审计:实现权限使用情况的审计日志
- 性能优化:学习如何优化Shiro的性能
📚 推荐资源
- 官方文档:Apache Shiro Documentation
- GitHub仓库:apache/shiro
- 中文教程:Apache Shiro中文文档
- 示例项目:本教程配套的代码示例
🎉恭喜你完成了Apache Shiro完整教程的学习!
通过本教程的学习,你已经掌握了Apache Shiro的核心概念和使用方法,能够在实际项目中灵活应用Shiro实现安全认证和授权。
建议你继续深入学习Shiro的高级特性,并在实际项目中应用所学知识,不断提升自己的安全开发能力。