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,轻松切换和组合不同的数据源
- 策略模式:认证、授权策略可配置
- 事件驱动:支持安全事件的监听和处理
- 注解支持:基于注解的声明式安全控制
优秀的性能表现
- 智能缓存:认证和授权结果的智能缓存
- 懒加载:安全对象的按需加载
- 连接池:数据库连接的有效管理
- 最小化锁:并发场景下的性能优化
🏗️ 核心架构
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 Shiro | Spring Security | Shiro优势 |
|---|---|---|---|
| 学习曲线 | 平缓 | 陡峭 | 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🏆 学习成果检验
✅ 实践任务
- 运行
SimplifiedAuthExample类,观察输出结果 - 修改用户名或密码,观察异常情况
- 尝试添加新用户到配置文件
第三章:角色管理(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🏆 学习成果检验
✅ 实践任务
- 运行
RoleExample类,观察角色检查结果 - 修改配置文件,为用户添加多个角色
- 尝试使用
hasRoles()和hasAllRoles()方法检查多个角色 - 使用
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🏆 学习成果检验
✅ 实践任务
- 运行
PermissionExample类,观察权限检查结果 - 修改配置文件,添加更多权限
- 尝试使用通配符权限
- 使用
isPermittedAll()方法检查多个权限 - 使用
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🏆 学习成果检验
✅ 实践任务
- 运行
SessionExample类,观察会话操作结果 - 尝试在会话中存储和获取更多属性
- 学习会话的生命周期管理
第六章:密码加密(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🏆 学习成果检验
✅ 实践任务
- 运行
EncryptionExample类,观察不同加密算法的结果 - 尝试使用不同的盐值和迭代次数
- 理解盐值加密的重要性
第七章:自定义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🏆 学习成果检验
✅ 实践任务
- 运行
CustomRealmExample类,观察自定义Realm的认证和授权结果 - 尝试修改模拟数据库中的用户、角色和权限信息
- 学习如何实现更复杂的自定义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📚 缓存配置要点
选择合适的缓存管理器:
- MemoryConstrainedCacheManager:基于内存的简单缓存管理器,适合开发和测试环境
- EhCacheManager:基于Ehcache的缓存管理器,适合单机环境
- SpringCacheManager:与Spring Cache集成,适合Spring项目
- RedisCacheManager:基于Redis的缓存管理器,适合分布式环境
配置缓存参数:
- 设置缓存名称
- 设置缓存过期时间
- 设置缓存大小限制
- 配置缓存刷新策略
缓存清理:
- 手动清理缓存
- 配置自动清理策略
- 监听缓存事件
📝 配置文件
项目配置文件: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 = *:*🏆 学习成果检验
✅ 实践任务
- 运行
CacheExample类,观察缓存功能的效果 - 尝试使用不同的缓存管理器
- 修改缓存配置参数,观察不同配置的效果
- 实现缓存清理功能
- 学习如何监控缓存使用情况
第九章:JWT集成
🎯 学习目标
- 理解JWT的概念和优势
- 掌握Shiro集成JWT的方法
- 学习JWT令牌的生成和验证
- 了解无状态认证的实现
- 掌握JWTRealm的开发
- 学习JWT工具类的实现
📦 JWT概述
JWT(JSON Web Token)是一种用于在网络应用间传递声明的基于JSON的开放标准:
- 无状态:服务器不需要存储会话信息,适合分布式系统
- 自包含:令牌中包含了所有必要的用户信息
- 跨平台:支持不同语言和平台
- 安全:支持签名和加密
- 可扩展:可以自定义声明内容
🔍 JWT结构
JWT令牌由三部分组成,用点(.)分隔:
- Header:包含令牌类型和签名算法
- Payload:包含声明信息(如用户名、过期时间等)
- 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🏆 学习成果检验
✅ 实践任务
- 运行
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的高级特性,并在实际项目中应用所学知识,不断提高自己的安全开发能力。
- 本文链接: https://richfan.eu.org/posts/%E7%A8%8B%E6%8A%80/java/apache-shiro-%E6%95%99%E7%A8%8B/
- 版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。
