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认证流程
- 学习如何配置用户账户
- 掌握异常处理方法
🔐 认证流程详解
认证架构设计
💡 常见认证异常
| 异常类型 | 说明 | 处理方式 |
|---|---|---|
| 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("用户已认证");
}
}
}
🔄 代码执行逻辑
📝 配置文件
项目配置文件: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
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());
}
}
}
🔄 代码执行逻辑
📝 配置文件
项目配置文件: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);
}
}
🔄 代码执行逻辑
📝 配置文件
项目配置文件: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);
}
}
🔄 代码执行逻辑
📝 配置文件
项目配置文件: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);
}
}
🔄 代码执行逻辑
🏆 学习成果检验
✅ 实践任务
- 运行
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;
}
}
}
🔄 代码执行逻辑
🏆 学习成果检验
✅ 实践任务
- 运行
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{认证缓存命中?}
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实现架构
💻 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);
}
}
🔄 代码执行逻辑
🏆 学习成果检验
✅ 实践任务
- 运行
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的高级特性,并在实际项目中应用所学知识,不断提高自己的安全开发能力。
604

被折叠的 条评论
为什么被折叠?



