Apache Shiro 教程

13437 字
67 分钟
Apache Shiro 教程

Apache Shiro 完整教程#

📚 教程目标:通过理论学习和代码实践,全面掌握Apache Shiro的核心功能、架构设计和最佳实践,能够在实际项目中灵活应用Shiro实现安全认证和授权。


📖 学习路径导航#

章节结构#

  1. 第一章:Shiro概述与基础架构 - 了解Shiro是什么、核心架构和设计理念
  2. 第二章:身份认证(Authentication) - 学习用户登录认证的实现
  3. 第三章:角色管理(Roles) - 掌握基于角色的授权机制
  4. 第四章:权限控制(Permissions) - 深入学习细粒度权限控制
  5. 第五章:会话管理(Session Management) - 了解Shiro的会话管理功能
  6. 第六章:密码加密(Encryption) - 学习安全的密码加密技术
  7. 第七章:自定义Realm - 掌握自定义数据源连接
  8. 第八章:缓存支持(Cache) - 了解如何优化Shiro性能
  9. 第九章:JWT集成 - 学习现代无状态认证方式

🎯 教程核心价值#

通过本教程的学习,你将能够:

掌握Shiro核心概念 - 理解认证、授权、会话等基础理论 ✅ 熟悉架构设计 - 了解SecurityManager、Subject、Realm等核心组件 ✅ 具备实践能力 - 能够编写完整的Shiro应用 ✅ 解决实际问题 - 应对各种安全场景 ✅ 做出技术选型 - 根据项目需求选择合适的安全方案


第一章:Shiro概述与基础架构#

🎯 学习目标#

  • 理解Apache Shiro是什么
  • 了解Shiro的发展历史和设计理念
  • 掌握Shiro的核心架构和组件
  • 明确Shiro在现代应用中的定位
  • 理解Shiro的核心优势

🔍 什么是Apache Shiro?#

Apache Shiro 是一个功能强大且易于使用的Java安全框架,提供了认证、授权、会话管理和加密等功能,用于保护任何类型的应用程序 - 从命令行工具到大型企业级Web应用。

📚 设计理念#

“简单即美” - Shiro的设计哲学是让复杂的安全操作变得简单直观

🌟 Shiro核心优势#

  1. 简洁直观的API设计

    • 最少知识原则:开发者只需要了解必要的API
    • 约定优于配置:合理的默认配置,减少配置负担
    • 分层抽象:不同层次的抽象满足不同复杂度的需求
  2. 全面的安全功能覆盖

    • 认证:多因素认证、JWT
    • 授权:基于角色、权限、资源的细粒度控制
    • 会话:支持Web和非Web环境、分布式会话
    • 加密:支持MD5、SHA、AES等主流加密算法
  3. 灵活的架构设计

    • 模块化架构:插件式Realm,轻松切换和组合不同的数据源
    • 策略模式:认证、授权策略可配置
    • 事件驱动:支持安全事件的监听和处理
    • 注解支持:基于注解的声明式安全控制
  4. 优秀的性能表现

    • 智能缓存:认证和授权结果的智能缓存
    • 懒加载:安全对象的按需加载
    • 连接池:数据库连接的有效管理
    • 最小化锁:并发场景下的性能优化

🏗️ 核心架构#

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

🔄 核心组件交互流程#

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 ShiroSpring SecurityShiro优势
学习曲线平缓陡峭API更简洁直观
配置复杂度简单复杂配置更直观
独立运行支持依赖Spring可独立使用
Web支持完善完善同样强大
非Web支持优秀一般更好的通用性
缓存机制内置需集成缓存更完善
会话管理强大一般会话功能更丰富
加密功能完善需集成加密功能更完整
授权灵活性极高平衡灵活与简洁
社区活跃度稳定活跃成熟稳定

选择建议#

选择Shiro的场景

  • 需要独立运行的安全框架
  • 项目不基于Spring框架
  • 需要丰富的会话管理功能
  • 追求简洁的API和配置
  • 需要非Web环境的安全支持

选择Spring Security的场景

  • 项目已基于Spring框架
  • 需要与Spring生态深度集成
  • 团队已熟悉Spring技术栈
  • 需要更细粒度的安全控制

🏆 学习成果检验#

✅ 基础理解#

  • 能够解释Shiro的核心概念
  • 能够描述Shiro的三层架构
  • 能够说出核心组件的作用
  • 能够列举Shiro的核心优势

第二章:身份认证(Authentication)#

🎯 学习目标#

  • 理解身份认证的概念
  • 掌握Shiro认证流程
  • 学习如何配置用户账户
  • 掌握异常处理方法

🔐 认证流程详解#

认证架构设计#

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

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;
/**
* Apache Shiro 简单身份认证示例类
* 开发思路:
* 1. 首先配置安全管理器和领域(Realm)
* 2. 获取当前用户主体(Subject)
* 3. 创建身份令牌进行用户认证
* 4. 处理各种认证异常情况
*
* @author 李昊哲 李胜龙
* @version 1.0.0
*/
public class SimplifiedAuthExample {
// 创建日志记录器,用于输出程序运行信息
private static final Logger LOGGER = LoggerFactory.getLogger(SimplifiedAuthExample.class);
/**
* 程序入口方法
* 开发过程:
* 1. 初始化Shiro安全框架组件
* 2. 进行用户身份认证
* 3. 处理认证结果和异常情况
*
* @param args 命令行参数
*/
public static void main(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配置文件的身份认证和授权实现
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
// 创建 SecurityManager 安全管理器实例,并传入realm
// SecurityManager是Shiro框架的核心,负责协调内部安全组件
DefaultSecurityManager securityManager = new DefaultSecurityManager(iniRealm);
// 将 SecurityManager 绑定到 SecurityUtils 工具类
// 这样在整个应用程序中都可以通过SecurityUtils获取SecurityManager
SecurityUtils.setSecurityManager(securityManager);
// 获取当前执行用户(Subject),Subject代表当前正在与软件进行交互的用户
Subject currentUser = SecurityUtils.getSubject();
// 判断当前用户是否已经通过认证
if (!currentUser.isAuthenticated()) {
// 如果未认证,则创建UsernamePasswordToken令牌,用于封装用户身份信息
// 这里使用预设的用户名"user"和密码"password"
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("用户已认证");
}
}
}

🔄 代码执行逻辑#

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

# 用户配置部分,定义系统中的用户及其密码
[users]
# 格式: 用户名 = 密码
user = password

🏆 学习成果检验#

✅ 实践任务#

  1. 运行 SimplifiedAuthExample 类,观察输出结果
  2. 修改用户名或密码,观察异常情况
  3. 尝试添加新用户到配置文件

第三章:角色管理(Roles)#

🎯 学习目标#

  • 理解角色的概念
  • 掌握角色分配方法
  • 学习角色检查机制
  • 了解角色与权限的关系

🧩 角色授权模型#

RBAC模型深度解析#

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

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;
/**
* Apache Shiro 角色权限控制示例类
* 开发思路:
* 1. 首先配置安全管理器和领域(Realm)
* 2. 获取当前用户主体(Subject)
* 3. 创建身份令牌进行用户认证
* 4. 验证用户角色
*
* @author 李昊哲 李胜龙
* @version 1.0.0
*/
public class RoleExample {
// 创建日志记录器,用于输出程序运行信息
private static final Logger LOGGER = LoggerFactory.getLogger(RoleExample.class);
/**
* 程序入口方法
* 开发过程:
* 1. 初始化Shiro安全框架组件,加载角色配置
* 2. 进行用户身份认证
* 3. 验证用户角色权限检查功能
*
* @param args 命令行参数
*/
public static void main(String[] args) {
// 创建 IniRealm 实例,使用专门的角色配置文件shiro-role.ini
// 该配置文件中包含了用户角色相关信息
IniRealm iniRealm = new IniRealm("classpath:shiro-role.ini");
// 创建 SecurityManager 安全管理器实例,并传入realm
// SecurityManager是Shiro框架的核心,负责协调内部安全组件
DefaultSecurityManager securityManager = new DefaultSecurityManager(iniRealm);
// 将 SecurityManager 绑定到 SecurityUtils 工具类
// 这样在整个应用程序中都可以通过SecurityUtils获取SecurityManager
SecurityUtils.setSecurityManager(securityManager);
// 获取当前执行用户(Subject),Subject代表当前正在与软件进行交互的用户
Subject currentUser = SecurityUtils.getSubject();
// 判断当前用户是否已经通过认证
if (!currentUser.isAuthenticated()) {
// 如果未认证,则创建UsernamePasswordToken令牌,用于封装用户身份信息
// 这里使用预设的用户名"user"和密码"password"
UsernamePasswordToken token = new UsernamePasswordToken("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());
}
}
}

🔄 代码执行逻辑#

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

# 用户配置部分,用于定义系统中的用户、密码及其所属角色
[users]
# 格式: 用户名 = 密码, 角色1, 角色2...
# 示例配置了一个用户名为"user",密码为"password"的用户,该用户属于"role"角色
user = password, role
# 角色权限配置部分,用于定义角色所拥有的权限
[roles]
# 格式: 角色名 = 权限
# 示例配置了"role"角色拥有"permission"权限
role = permission

🏆 学习成果检验#

✅ 实践任务#

  1. 运行 RoleExample 类,观察角色检查结果
  2. 修改配置文件,为用户添加多个角色
  3. 尝试使用 hasRoles()hasAllRoles() 方法检查多个角色
  4. 使用 checkRole() 方法检查角色

第四章:权限控制(Permissions)#

🎯 学习目标#

  • 理解权限的概念和格式
  • 掌握细粒度权限控制
  • 学习权限表达式语法
  • 了解权限检查方法
  • 学习通配符权限

🎮 权限表达式语法详解#

权限格式定义#

权限格式:资源:操作:实例
示例:
- 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

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;
/**
* Apache Shiro 权限控制示例类
* 开发思路:
* 1. 配置支持权限的Realm和安全管理器
* 2. 实现用户身份认证
* 3. 验证用户细粒度权限控制功能
*
* @author 李昊哲 李胜龙
* @version 1.0.0
*/
public class PermissionExample {
// 创建日志记录器,用于输出程序运行信息
private static final Logger LOGGER = LoggerFactory.getLogger(PermissionExample.class);
/**
* 程序入口方法
* 开发过程:
* 1. 初始化Shiro安全框架组件,加载权限配置
* 2. 进行用户身份认证
* 3. 验证用户权限检查功能
*
* @param args 命令行参数
*/
public static void main(String[] args) {
// 创建 IniRealm 实例,使用专门的权限配置文件shiro-permission.ini
// 该配置文件中包含了用户、角色及具体权限的映射关系
IniRealm iniRealm = new IniRealm("classpath:shiro-permission.ini");
// 创建 SecurityManager 安全管理器实例,并传入realm
// SecurityManager是Shiro框架的核心,负责协调内部安全组件
DefaultSecurityManager securityManager = new DefaultSecurityManager(iniRealm);
// 将 SecurityManager 绑定到 SecurityUtils 工具类
// 这样在整个应用程序中都可以通过SecurityUtils获取SecurityManager
SecurityUtils.setSecurityManager(securityManager);
// 获取当前执行用户(Subject),Subject代表当前正在与软件进行交互的用户
Subject currentUser = SecurityUtils.getSubject();
// 判断当前用户是否已经通过认证
if (!currentUser.isAuthenticated()) {
// 如果未认证,则创建UsernamePasswordToken令牌,用于封装用户身份信息
// 这里使用预设的用户名"user"和密码"password"
UsernamePasswordToken token = new UsernamePasswordToken("user", "password");
// 使用令牌进行用户登录认证
currentUser.login(token);
// 认证成功后,记录用户登录成功的日志信息
LOGGER.info("用户 {} 登录成功!", currentUser.getPrincipal());
}
// 检查用户是否具有"resource:action:object"权限
// isPermitted方法用于判断当前用户是否拥有指定权限
// 权限格式通常采用"资源:操作:对象"的形式表示
if (currentUser.isPermitted("resource:action:object")) {
LOGGER.info("用户 {} 拥有权限 resource:action:object", currentUser.getPrincipal());
} else {
LOGGER.info("用户 {} 没有权限 resource:action:object", currentUser.getPrincipal());
}
// 检查用户是否具有"another:resource:action"权限
if (currentUser.isPermitted("another:resource:action")) {
LOGGER.info("用户 {} 拥有权限 another:resource:action", currentUser.getPrincipal());
} else {
LOGGER.info("用户 {} 没有权限 another:resource:action", currentUser.getPrincipal());
}
// 正常退出程序
System.exit(0);
}
}

🔄 代码执行逻辑#

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

# 用户配置部分,用于定义系统中的用户、密码及其所属角色
[users]
# 格式: 用户名 = 密码, 角色1, 角色2...
# 示例配置了一个用户名为"user",密码为"password"的用户
# 该用户同时属于"role1"和"role2"两个角色
user = password, role1, role2
# 角色权限配置部分,用于定义角色所拥有的权限
[roles]
# 格式: 角色名 = 权限表达式
# 权限表达式通常采用"资源:操作:对象"的格式来描述
# role1角色具有对资源(resource)执行动作(action)的权限,作用对象为object
role1 = resource:action:object
# role2角色具有对资源(another)执行动作(resource)的权限,作用对象为action
role2 = another:resource:action

🏆 学习成果检验#

✅ 实践任务#

  1. 运行 PermissionExample 类,观察权限检查结果
  2. 修改配置文件,添加更多权限
  3. 尝试使用通配符权限
  4. 使用 isPermittedAll() 方法检查多个权限
  5. 使用 checkPermission() 方法检查权限

第五章:会话管理(Session Management)#

🎯 学习目标#

  • 理解Shiro会话的概念
  • 掌握会话操作方法
  • 学习会话属性管理
  • 了解会话生命周期

🌐 会话管理概述#

Shiro提供了强大的会话管理功能,支持Web和非Web环境,具有以下特点:

  • 跨环境支持:相同的API在Web和非Web环境下均可使用
  • 丰富的会话操作:支持会话属性管理、超时设置等
  • 会话持久化:支持将会话存储到不同的数据源
  • 会话验证:自动验证会话的有效性

💻 代码示例#

项目代码文件src/main/java/com/shiro/tutorial/SessionExample.java

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;
/**
* Apache Shiro 会话管理示例类
* 开发思路:
* 1. 配置支持会话管理的Realm和安全管理器
* 2. 实现用户身份认证
* 3. 演示Shiro会话管理功能的使用
*
* @author 李昊哲 李胜龙
* @version 1.0.0
*/
public class SessionExample {
// 创建日志记录器,用于输出程序运行信息
private static final Logger LOGGER = LoggerFactory.getLogger(SessionExample.class);
/**
* 程序入口方法,演示Shiro会话管理功能
* 功能流程:
* 1. 初始化Shiro核心组件和配置
* 2. 进行用户身份认证
* 3. 演示会话的创建、属性操作、过期管理和失效处理
*
* @param args 命令行参数
*/
public static void main(String[] args) {
// 创建 IniRealm 实例,使用专门的权限配置文件shiro-session.ini
// 该配置文件中包含了用户、角色及具体权限的映射关系
IniRealm iniRealm = new IniRealm("classpath:shiro-session.ini");
// 创建 SecurityManager 安全管理器实例,并传入realm
// SecurityManager是Shiro框架的核心,负责协调内部安全组件
DefaultSecurityManager securityManager = new DefaultSecurityManager(iniRealm);
// 将 SecurityManager 绑定到 SecurityUtils 工具类
// 这样在整个应用程序中都可以通过SecurityUtils获取SecurityManager
SecurityUtils.setSecurityManager(securityManager);
// 获取当前执行用户(Subject),Subject代表当前正在与软件进行交互的用户
Subject currentUser = SecurityUtils.getSubject();
// 判断当前用户是否已经通过认证
if (!currentUser.isAuthenticated()) {
// 如果未认证,则创建UsernamePasswordToken令牌,用于封装用户身份信息
// 这里使用预设的用户名"user"和密码"password"
UsernamePasswordToken token = new UsernamePasswordToken("user", "password");
// 使用令牌进行用户登录认证
currentUser.login(token);
// 认证成功后,记录用户登录成功的日志信息
LOGGER.info("用户 {} 登录成功!", currentUser.getPrincipal());
}
// 获取当前用户的会话(Session)对象
// Session是Shiro提供的会话管理接口,类似于Web中的HttpSession
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);
// ==================== 会话高级功能演示 ====================
// 1. 会话标识与时间信息
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);
// 2. 会话过期时间管理
session.setTimeout(3600000); // 设置会话过期时间为1小时
LOGGER.info("会话过期时间已设置为1小时");
long timeout = session.getTimeout();
LOGGER.info("当前会话过期时间:{}毫秒", timeout);
// 3. 会话过期检查
// Shiro中Session接口没有直接的isExpired()方法
// 会话过期通常在访问时自动检查,过期会抛出ExpiredSessionException
try {
session.getAttribute("username"); // 尝试访问会话属性,自动检查过期
LOGGER.info("会话未过期");
} catch (org.apache.shiro.session.ExpiredSessionException e) {
LOGGER.info("会话已过期");
}
// 4. 会话活动管理
session.touch(); // 更新会话最后访问时间
LOGGER.info("会话最后访问时间已更新");
// 5. 会话失效处理
session.stop(); // 手动使会话失效
LOGGER.info("会话已失效");
// 正常退出程序
System.exit(0);
}
}

🔄 代码执行逻辑#

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

# 用户配置,格式: 用户名 = 密码
[users]
user = password

🏆 学习成果检验#

✅ 实践任务#

  1. 运行 SessionExample 类,观察会话操作结果
  2. 尝试在会话中存储和获取更多属性
  3. 学习会话的生命周期管理

第六章:密码加密(Encryption)#

🎯 学习目标#

  • 理解密码加密的重要性
  • 掌握Shiro加密API的使用
  • 学习盐值加密技术
  • 了解不同加密算法的特点

🔒 密码加密概述#

密码加密是保护用户数据安全的重要手段,Shiro提供了强大的加密支持:

  • 支持多种加密算法:MD5、SHA-1、SHA-256等
  • 内置盐值加密支持
  • 支持多次迭代加密

💻 代码示例#

项目代码文件src/main/java/com/shiro/tutorial/EncryptionExample.java

package com.shiro.tutorial;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Apache Shiro 加密示例类
* 开发思路:
* 1. 演示如何使用Shiro进行密码加密
* 2. 展示盐值加密的重要性
* 3. 演示加密密码的验证过程
*
* @author 李昊哲 李胜龙
* @version 1.0.0
*/
public class EncryptionExample {
// 创建日志记录器,用于输出程序运行信息
private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionExample.class);
/**
* 程序入口方法
* 开发过程:
* 1. 演示明文密码加密过程
* 2. 展示带盐值的密码加密
* 3. 演示加密密码的验证
*
* @param args 命令行参数
*/
public static void main(String[] args) {
// 原始密码
String originalPassword = "password";
// 使用MD5算法加密密码
String md5Hash = new SimpleHash("MD5", originalPassword).toHex();
LOGGER.info("原始密码:{},MD5加密结果:{}", originalPassword, md5Hash);
// 使用SHA-256算法加密密码
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);
}
}

🔄 代码执行逻辑#

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

🏆 学习成果检验#

✅ 实践任务#

  1. 运行 EncryptionExample 类,观察不同加密算法的结果
  2. 尝试使用不同的盐值和迭代次数
  3. 理解盐值加密的重要性

第七章:自定义Realm#

🎯 学习目标#

  • 理解Realm的作用
  • 掌握自定义Realm的开发方法
  • 学习如何实现认证和授权逻辑
  • 了解多Realm配置

🗄️ 自定义Realm概述#

Realm是Shiro与数据源之间的桥梁,负责从数据源获取安全数据。自定义Realm允许我们:

  • 从任意数据源获取数据
  • 实现自定义的认证逻辑
  • 实现自定义的授权逻辑

💻 代码示例#

项目代码文件src/main/java/com/shiro/tutorial/CustomRealmExample.java

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;
/**
* Apache Shiro 自定义Realm示例类
* 开发思路:
* 1. 创建自定义Realm类继承AuthorizingRealm
* 2. 实现认证和授权方法
* 3. 配置安全管理器使用自定义Realm
* 4. 验证自定义Realm功能
*
* @author 李昊哲 李胜龙
* @version 1.0.0
* @since 2025-12-15
*/
public class CustomRealmExample {
// 创建日志记录器,用于输出程序运行信息
private static final Logger LOGGER = LoggerFactory.getLogger(CustomRealmExample.class);
/**
* 程序入口方法
* 开发过程:
* 1. 初始化自定义Realm
* 2. 配置安全管理器
* 3. 进行用户身份认证和授权验证
*
* @param args 命令行参数
*/
public static void main(String[] args) {
// 创建自定义 Realm 实例
UserRealm userRealm = new UserRealm();
// 创建 SecurityManager 安全管理器实例,并传入realm
// SecurityManager是Shiro框架的核心,负责协调内部安全组件
DefaultSecurityManager securityManager = new DefaultSecurityManager(userRealm);
// 将 SecurityManager 绑定到 SecurityUtils 工具类
// 这样在整个应用程序中都可以通过SecurityUtils获取SecurityManager
SecurityUtils.setSecurityManager(securityManager);
// 获取当前执行用户(Subject),Subject代表当前正在与软件进行交互的用户
Subject currentUser = SecurityUtils.getSubject();
// 判断当前用户是否已经通过认证
if (!currentUser.isAuthenticated()) {
// 如果未认证,则创建UsernamePasswordToken令牌,用于封装用户身份信息
// 这里使用预设的用户名"user"和密码"password"
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("用户已认证");
}
// 验证用户是否有 admin 角色
if (currentUser.hasRole("admin")) {
LOGGER.info("用户具有 admin 角色");
} else {
LOGGER.info("用户不具有 admin 角色");
}
// 验证用户是否有user:delete权限
if (currentUser.isPermitted("user:delete")) {
LOGGER.info("用户有删除用户的权限");
} else {
LOGGER.info("用户没有删除用户的权限");
}
// 验证用户是否有product:create权限
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);
}
/**
* 认证方法:获取认证信息
*
* @param authenticationToken 认证令牌
* @return 认证信息
* @throws AuthenticationException 认证异常
*/
@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());
}
/**
* 授权方法:获取授权信息
*
* @param principalCollection 身份信息
* @return 授权信息
*/
@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;
}
}
}

🔄 代码执行逻辑#

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

🏆 学习成果检验#

✅ 实践任务#

  1. 运行 CustomRealmExample 类,观察自定义Realm的认证和授权结果
  2. 尝试修改模拟数据库中的用户、角色和权限信息
  3. 学习如何实现更复杂的自定义Realm

第八章:缓存支持(Cache)#

🎯 学习目标#

  • 理解Shiro缓存的概念和作用
  • 掌握Shiro缓存管理器的配置
  • 学习如何启用和配置认证缓存
  • 学习如何启用和配置授权缓存
  • 了解不同缓存管理器的特点

📦 缓存概述#

Shiro提供了强大的缓存支持,可以显著提高系统性能,减少数据库访问次数。缓存主要应用于以下场景:

  • 认证缓存:缓存用户的认证信息,避免重复查询数据库
  • 授权缓存:缓存用户的角色和权限信息,避免重复查询数据库
  • 会话缓存:缓存用户会话信息,支持分布式会话管理

🔧 项目缓存实现#

项目代码文件src/main/java/com/shiro/tutorial/CacheExample.java

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;
/**
* Apache Shiro 缓存支持示例类
* 开发思路:
* 1. 配置支持缓存的Realm和安全管理器
* 2. 实现用户身份认证
* 3. 演示Shiro缓存功能的使用
*/
public class CacheExample {
// 创建日志记录器,用于输出程序运行信息
private static final Logger log = LoggerFactory.getLogger(CacheExample.class);
/**
* 程序入口方法
* 开发过程:
* 1. 初始化Shiro安全框架组件,配置缓存管理器
* 2. 进行用户身份认证
* 3. 演示缓存功能
*/
public static void main(String[] args) {
// 创建 IniRealm 实例,使用专门的会话配置文件shiro-session.ini
IniRealm iniRealm = new IniRealm("classpath:shiro-session.ini");
// 启用缓存 - 设置缓存管理器
iniRealm.setCacheManager(new MemoryConstrainedCacheManager());
// 启用认证缓存
iniRealm.setAuthenticationCachingEnabled(true);
// 启用授权缓存
iniRealm.setAuthorizationCachingEnabled(true);
// 创建 SecurityManager 安全管理器实例,并传入realm
DefaultSecurityManager securityManager = new DefaultSecurityManager(iniRealm);
// 将 SecurityManager 绑定到 SecurityUtils 工具类
SecurityUtils.setSecurityManager(securityManager);
// 获取当前执行用户(Subject)
Subject currentUser = SecurityUtils.getSubject();
// 判断当前用户是否已经通过认证
if (!currentUser.isAuthenticated()) {
// 如果未认证,则创建UsernamePasswordToken令牌
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);
}
}

🔄 代码执行逻辑#

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[退出程序]

📦 缓存工作原理#

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

📚 缓存配置要点#

  1. 选择合适的缓存管理器

    • MemoryConstrainedCacheManager:基于内存的简单缓存管理器,适合开发和测试环境
    • EhCacheManager:基于Ehcache的缓存管理器,适合单机环境
    • SpringCacheManager:与Spring Cache集成,适合Spring项目
    • RedisCacheManager:基于Redis的缓存管理器,适合分布式环境
  2. 配置缓存参数

    • 设置缓存名称
    • 设置缓存过期时间
    • 设置缓存大小限制
    • 配置缓存刷新策略
  3. 缓存清理

    • 手动清理缓存
    • 配置自动清理策略
    • 监听缓存事件

📝 配置文件#

项目配置文件src/main/resources/shiro-session.ini

# 用户配置部分,用于定义系统中的用户及其密码
[users]
# 格式: 用户名 = 密码
# 示例配置了一个用户名为"user",密码为"password"的用户,属于user_role角色
user = password, user_role
# 角色权限配置部分,用于定义角色所拥有的权限
# 格式: 角色名 = 权限表达式
[roles]
# user_role角色拥有product:view和product:create权限
user_role = product:view, product:create
# admin_role角色拥有所有权限
admin_role = *:*

🏆 学习成果检验#

✅ 实践任务#

  1. 运行 CacheExample 类,观察缓存功能的效果
  2. 尝试使用不同的缓存管理器
  3. 修改缓存配置参数,观察不同配置的效果
  4. 实现缓存清理功能
  5. 学习如何监控缓存使用情况

第九章:JWT集成#

🎯 学习目标#

  • 理解JWT的概念和优势
  • 掌握Shiro集成JWT的方法
  • 学习JWT令牌的生成和验证
  • 了解无状态认证的实现
  • 掌握JWTRealm的开发
  • 学习JWT工具类的实现

📦 JWT概述#

JWT(JSON Web Token)是一种用于在网络应用间传递声明的基于JSON的开放标准:

  • 无状态:服务器不需要存储会话信息,适合分布式系统
  • 自包含:令牌中包含了所有必要的用户信息
  • 跨平台:支持不同语言和平台
  • 安全:支持签名和加密
  • 可扩展:可以自定义声明内容

🔍 JWT结构#

JWT令牌由三部分组成,用点(.)分隔:

  1. Header:包含令牌类型和签名算法
  2. Payload:包含声明信息(如用户名、过期时间等)
  3. Signature:使用密钥对前两部分进行签名,用于验证令牌的完整性

🏗️ 项目JWT实现架构#

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

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;
/**
* JWT工具类
* 用于生成和验证JWT令牌
*
* @author 李昊哲 李胜龙
* @version 2.0.6
*/
public class JwtUtils {
// 创建日志记录器,用于输出程序运行信息
private static final Logger log = LoggerFactory.getLogger(JwtUtils.class);
// JWT 签名密钥
public static final SecretKey SECRET_KEY = Jwts.SIG.HS256.key().build();
// 令牌过期时间(24小时)
private static final long EXPIRATION_TIME = 86400000;
/**
* 生成 JWT 令牌
*
* @param username 用户名
* @return JWT 令牌字符串
*/
public static String generateToken(String username) {
Date now = new Date();
Date expirationDate = new Date(now.getTime() + EXPIRATION_TIME);
// 参数含义 :主题、签发时间、过期时间、密钥
// compact 方法将JWT令牌转换为字符串并返回
return Jwts.builder()
.subject(username)
.issuedAt(now)
.expiration(expirationDate)
.signWith(SECRET_KEY)
.compact();
}
/**
* 从 JWT 令牌中提取用户名
*
* @param token JWT 令牌
* @return 用户名
*/
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;
}
}
/**
* 验证 JWT 令牌的有效性
*
* @param token JWT 令牌
* @return 令牌是否有效
*/
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

package com.shiro.tutorial.jwt;
import org.apache.shiro.authc.AuthenticationToken;
/**
* JWT令牌类
* 用于封装JWT令牌信息,实现Shiro的AuthenticationToken接口
*
* @author 李昊哲 李胜龙
* @version 2.0.6
*/
public class JwtToken implements AuthenticationToken {
/**
* JWT 令牌字符串
*/
private final String token;
/**
* 构造函数
*
* @param token JWT 令牌字符串
*/
public JwtToken(String token) {
this.token = token;
}
/**
* 获取用户身份信息
*
* @return JWT 令牌字符串
*/
@Override
public Object getPrincipal() {
return token;
}
/**
* 获取用户凭据信息
*
* @return JWT 令牌字符串
*/
@Override
public Object getCredentials() {
return token;
}
}

💻 JWT Realm实现#

项目代码文件src/main/java/com/shiro/tutorial/jwt/JwtRealm.java

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;
/**
* JWT Realm类
* 用于处理JWT令牌的认证和授权
*
* @author 李昊哲 李胜龙
* @version 2.0.6
*/
public class JwtRealm extends AuthorizingRealm {
// 创建日志记录器,用于输出程序运行信息
private static final Logger log = LoggerFactory.getLogger(JwtRealm.class);
/**
* 设置 Realm 支持的AuthenticationToken类型
*
* @param token 认证令牌
* @return 是否支持该令牌类型
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* 认证方法:获取认证信息
*
* @param authToken 认证令牌
* @return 认证信息
* @throws AuthenticationException 认证异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
log.info("JWT Realm 开始认证...");
// 获取 JWT 令牌
String token = (String) authToken.getCredentials();
// 验证 JWT 令牌
if (!validateToken(token)) {
throw new AuthenticationException("无效的 JWT 令牌");
}
// 从 JWT 令牌中获取用户名
String username = getUsernameFromToken(token);
// 返回认证信息
return new SimpleAuthenticationInfo(username, token, getName());
}
/**
* 授权方法:获取授权信息
*
* @param principals 身份信息
* @return 授权信息
*/
@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;
}
/**
* 从 JWT 令牌中提取用户名
*
* @param token JWT 令牌
* @return 用户名
*/
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;
}
}
/**
* 验证 JWT 令牌的有效性
*
* @param token JWT 令牌
* @return 令牌是否有效
*/
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;
}
}
/**
* 获取用户角色(模拟实现)
*
* @param username 用户名
* @return 用户角色集合
*/
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;
}
/**
* 获取用户权限(模拟实现)
*
* @param username 用户名
* @return 用户权限集合
*/
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

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;
/**
* Apache Shiro JWT集成示例类
* 开发思路:
* 1. 创建JWT工具类用于生成和验证JWT令牌
* 2. 创建JWT Realm处理JWT认证和授权
* 3. 配置安全管理器使用JWT Realm
* 4. 演示JWT令牌的生成和验证过程
*
* @author 李昊哲 李胜龙
* @version 2.0.6
*/
public class JwtShiroExample {
// 创建日志记录器,用于输出程序运行信息
private static final Logger log = LoggerFactory.getLogger(JwtShiroExample.class);
/**
* 程序入口方法
* 开发过程:
* 1. 初始化Shiro安全框架组件,使用JWT Realm
* 2. 生成JWT令牌
* 3. 使用JWT令牌进行用户身份认证和授权验证
*
* @param args 命令行参数
*/
public static void main(String[] args) {
// 创建JWT Realm实例
JwtRealm jwtRealm = new JwtRealm();
// 创建 SecurityManager 安全管理器实例,并传入JWT Realm
DefaultSecurityManager securityManager = new DefaultSecurityManager(jwtRealm);
// 将 SecurityManager 绑定到 SecurityUtils 工具类
SecurityUtils.setSecurityManager(securityManager);
// 获取当前执行用户(Subject)
Subject currentUser = SecurityUtils.getSubject();
// 生成JWT令牌(模拟用户登录成功后生成令牌)
String jwtToken = JwtUtils.generateToken("admin");
log.info("生成的JWT令牌: {}", jwtToken);
// 验证 JWT 令牌
boolean isValid = JwtUtils.validateToken(jwtToken);
log.info("JWT令牌是否有效: {}", isValid);
// 从 JWT 令牌中提取用户名
String username = JwtUtils.getUsernameFromToken(jwtToken);
log.info("从JWT令牌中提取的用户名: {}", username);
// 使用 JWT 令牌进行认证
JwtToken token = new JwtToken(jwtToken);
try {
// 使用 JWT 令牌进行用户登录认证
currentUser.login(token);
// 认证成功后,记录用户登录成功的日志信息
log.info("用户 {} 使用JWT令牌登录成功!", currentUser.getPrincipal());
} catch (Exception e) {
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("用户没有删除用户的权限");
}
// 验证用户是否有product:create权限
if (currentUser.isPermitted("product:create")) {
log.info("用户有创建产品的权限");
} else {
log.info("用户没有创建产品的权限");
}
// 正常退出程序
System.exit(0);
}
}

🔄 代码执行逻辑#

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

🏆 学习成果检验#

✅ 实践任务#

  1. 运行 JwtShiroExample 类,观察JWT集成效果
  2. 尝试修改JWT令牌的过期时间,观察令牌过期后的行为
  3. 学习如何在Web应用中使用JWT进行身份认证
  4. 了解如何实现JWT令牌的刷新机制
  5. 学习如何处理JWT令牌的注销操作

📚 总结与进阶#

🎯 核心知识点回顾#

  1. 认证(Authentication):验证用户身份,确认用户是否为系统合法用户
  2. 授权(Authorization):检查用户权限,确定用户可以访问哪些资源
  3. 角色(Role):权限的集合,用于批量分配权限给用户
  4. 权限(Permission):对资源的访问许可,使用”资源:操作:实例”格式
  5. 会话(Session):管理用户的会话状态,支持Web和非Web环境
  6. 加密(Encryption):保护用户密码安全,支持多种加密算法
  7. Realm:连接Shiro与数据源的桥梁,负责获取安全数据
  8. JWT:无状态认证方式,适合分布式系统

🚀 进阶学习建议#

  1. 深入学习Shiro源码:理解Shiro的内部实现机制
  2. 集成Spring Boot:学习如何在Spring Boot项目中使用Shiro
  3. 分布式会话管理:学习如何使用Redis等存储会话
  4. 多因素认证:实现更安全的认证方式
  5. 权限设计实践:学习如何设计合理的权限体系
  6. JWT高级应用:实现令牌刷新、黑名单等功能
  7. 性能优化:学习如何优化Shiro的性能

🎉 恭喜你完成了Apache Shiro完整教程的学习!

通过本教程的学习,你已经掌握了Apache Shiro的核心功能和使用方法,能够在实际项目中灵活应用Shiro实现安全认证和授权。

建议你继续深入学习Shiro的高级特性,并在实际项目中应用所学知识,不断提高自己的安全开发能力。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
Apache Shiro 教程
https://fanrich.eu.org/posts/程技/java/apache-shiro-教程/
作者
richfan
发布于
2026-06-26
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
richfan
Hello, I'm richfan.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
分类
标签
站点统计
文章
779
分类
8
标签
138
总字数
3,267,679
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.13.5
文章许可
CC BY-NC-SA 4.0

文章目录