Apache Shiro 教程

Apache Shiro 完整教程

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


📖 学习路径导航

章节结构

  1. 第一章:Shiro概述与基础架构 - 了解Shiro是什么、核心架构和设计理念
  2. 第二章:身份认证(Authentication) - 学习用户登录认证的实现
  3. 第三章:角色管理(Roles) - 掌握基于角色的授权机制
  4. 第四章:权限控制(Permissions) - 深入学习细粒度权限控制
  5. [第五章:会话管理(Session Management)](#第五章:会话管理(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. 优秀的性能表现

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

🏗️ 核心架构

数据层 - Data Layer 核心层 - Core Layer 应用层 - Application Layer Realm 数据源连接器 JDBC Realm 数据库 LDAP Realm 目录服务 Custom Realm 自定义数据源 Text Realm 文本配置 Authenticator 认证器 SecurityManager 安全管理器 Authorizer 授权器 SessionManager 会话管理器 CacheManager 缓存管理器 Cryptography 加密组件 AuthenticationStrategy 认证策略 CredentialMatcher 凭证匹配器 PermissionResolver 权限解析器 RolePermissionResolver 角色权限解析器 SessionDAO 会话存储 SessionFactory 会话工厂 SessionValidationScheduler 会话验证调度器 SecurityUtils 安全工具 Subject 当前用户

🔄 核心组件交互流程

应用程序 Subject SecurityManager Authenticator Authorizer Realm 数据源 创建认证请求 委托认证处理 执行认证 查询用户信息 获取用户凭证 返回用户数据 返回AuthenticationInfo 认证结果 认证成功/失败 返回认证状态 请求授权检查 委托授权处理 执行授权检查 查询角色权限 获取权限数据 返回权限数据 返回AuthorizationInfo 授权结果 授权成功/失败 返回授权状态 同时管理会话和加密操作 应用程序 Subject SecurityManager Authenticator Authorizer 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认证流程
  • 学习如何配置用户账户
  • 掌握异常处理方法

🔐 认证流程详解

认证架构设计

用户 应用程序 Subject SecurityManager Authenticator CredentialMatcher Realm 数据源 输入用户名密码 创建UsernamePasswordToken 调用login(token) 委托login(token) 执行认证 遍历配置的Realm 检查Realm是否支持此token 支持 执行doGetAuthenticationInfo 查询用户信息 返回用户数据(包含加密密码) 返回AuthenticationInfo 比较凭证 匹配成功 认证成功 设置认证状态 返回认证成功 登录成功 匹配失败 认证失败 抛出IncorrectCredentialsException 认证失败 密码错误 alt [密码匹配] [密码不匹配] 不支持 跳过此Realm,尝试下一个 alt [Realm支持此token] [Realm不支持此token] 抛出UnknownAccountException 认证失败 抛出异常 认证失败 用户名不存在 alt [用户名不存在] 抛出LockedAccountException 认证失败 抛出异常 认证失败 账户已锁定 alt [账户锁定] 用户 应用程序 Subject SecurityManager Authenticator CredentialMatcher Realm 数据源

💡 常见认证异常

异常类型 说明 处理方式
UnknownAccountException 用户名不存在 提示用户注册或检查用户名
IncorrectCredentialsException 密码错误 提示用户重新输入密码
LockedAccountException 账户已锁定 联系管理员解锁账户
DisabledAccountException 账户已禁用 联系管理员启用账户
AuthenticationException 其他认证异常 通用异常处理

💻 代码示例

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

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("用户已认证");
    }
  }
}

🔄 代码执行逻辑

是 否 是 否 UnknownAccountException IncorrectCredentialsException 其他 创建IniRealm实例 加载shiro.ini配置文件 创建DefaultSecurityManager实例 将IniRealm传入SecurityManager 将SecurityManager绑定到SecurityUtils 获取当前用户Subject 检查用户是否已认证 记录用户已认证日志 创建UsernamePasswordToken 使用token进行登录认证 登录成功? 记录登录成功日志 异常类型? 记录用户名不存在日志 记录密码错误日志 记录认证失败日志 退出程序

📝 配置文件

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

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

🏆 学习成果检验

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

第三章:角色管理(Roles)

🎯 学习目标

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

🧩 角色授权模型

RBAC模型深度解析

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 操作类型 style 分配到 包含用户 拥有权限 分配给角色

🎯 角色检查方法

方法 说明 返回值
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

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());
    }
  }
}

🔄 代码执行逻辑

是 否 是 否 是 否 创建IniRealm实例 加载shiro-role.ini配置文件 创建DefaultSecurityManager实例 将IniRealm传入SecurityManager 将SecurityManager绑定到SecurityUtils 获取当前用户Subject 检查用户是否已认证 检查用户是否具有'role'角色 创建UsernamePasswordToken 使用token进行登录认证 记录用户登录成功日志 输出'用户拥有角色 role'日志 输出'用户没有角色 role'日志 检查用户是否具有'admin'角色 输出'用户拥有角色 admin'日志 输出'用户没有角色 admin'日志 程序结束

📝 配置文件

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

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

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);
  }
}

🔄 代码执行逻辑

是 否 是 否 是 否 初始化配置 创建IniRealm实例 加载shiro-permission.ini配置文件 创建DefaultSecurityManager实例 将IniRealm传入SecurityManager 将SecurityManager绑定到SecurityUtils 认证流程 获取当前用户Subject 检查用户是否已认证 跳过认证 创建UsernamePasswordToken 使用token进行登录认证 记录用户登录成功日志 授权流程 检查用户是否具有'resource:action:object'权限 输出'用户拥有权限 resource:action:object'日志 输出'用户没有权限 resource:action:object'日志 检查用户是否具有'another:resource:action'权限 输出'用户拥有权限 another:resource:action'日志 输出'用户没有权限 another:resource:action'日志 退出程序

📝 配置文件

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

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

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);
  }
}

🔄 代码执行逻辑

是 否 初始化配置 创建IniRealm实例 加载shiro-session.ini配置文件 创建DefaultSecurityManager实例 将IniRealm传入SecurityManager 将SecurityManager绑定到SecurityUtils 认证流程 获取当前用户Subject 检查用户是否已认证 跳过认证 创建UsernamePasswordToken 使用token进行登录认证 记录用户登录成功日志 会话基本操作 获取Session对象 设置会话属性username 设置会话属性loginTime 获取会话属性username 获取会话属性loginTime 记录会话信息 移除会话属性loginTime 记录属性移除信息 会话高级操作 获取会话ID和时间信息 管理会话过期时间 检查会话是否过期 更新会话最后访问时间 手动使会话失效 退出程序

📝 配置文件

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

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

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);
  }
}

🔄 代码执行逻辑

是 否 是 否 定义原始密码 使用MD5算法加密 使用SHA-256算法加密 使用带盐值的SHA-256加密 输出MD5加密结果 输出SHA-256加密结果 输出带盐值SHA-256加密结果 密码验证流程 使用正确密码验证 验证成功? 输出验证成功日志 输出验证失败日志 使用错误密码验证 验证成功? 输出错误密码验证成功日志 输出错误密码验证失败日志 退出程序

🏆 学习成果检验

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

第七章:自定义Realm

🎯 学习目标

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

🗄️ 自定义Realm概述

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

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

💻 代码示例

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

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;
    }

  }
}

🔄 代码执行逻辑

是 否 是 否 是 否 是 否 是 否 初始化配置 创建自定义UserRealm实例 创建DefaultSecurityManager实例 将UserRealm传入SecurityManager 将SecurityManager绑定到SecurityUtils 认证流程 获取当前用户Subject 检查用户是否已认证 跳过认证 创建UsernamePasswordToken 执行登录认证 登录成功? 记录登录成功日志 记录认证失败日志 授权流程 检查用户是否具有admin角色 记录角色检查通过日志 记录角色检查失败日志 检查用户是否具有user:delete权限 记录权限检查通过日志 记录权限检查失败日志 检查用户是否具有product:create权限 记录权限检查通过日志 记录权限检查失败日志 退出程序

🏆 学习成果检验

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

第八章:缓存支持(Cache)

🎯 学习目标

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

📦 缓存概述

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

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

🔧 项目缓存实现

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

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);
  }
}

🔄 代码执行逻辑

是 否 是 否 初始化配置 创建IniRealm实例 加载shiro-session.ini配置文件 配置MemoryConstrainedCacheManager 启用认证缓存 启用授权缓存 创建DefaultSecurityManager实例 将IniRealm传入SecurityManager 将SecurityManager绑定到SecurityUtils 认证流程 获取当前用户Subject 检查用户是否已认证 跳过认证 创建UsernamePasswordToken 使用token进行登录认证 登录成功? 记录用户登录成功日志 记录认证失败日志 缓存演示流程 第一次检查admin角色 记录第一次角色检查结果 第二次检查admin角色 记录第二次角色检查结果 第一次检查user:create权限 记录第一次权限检查结果 第二次检查user:create权限 记录第二次权限检查结果 输出缓存说明信息 退出程序

📦 缓存工作原理

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

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实现架构

JwtUtils 生成JWT令牌 验证JWT令牌 提取用户名 JwtToken 封装JWT令牌 实现AuthenticationToken接口 JwtRealm doGetAuthenticationInfo doGetAuthorizationInfo validateToken getUserRoles getUserPermissions JwtShiroExample 初始化JWT Realm 生成并验证JWT令牌 使用JWT令牌登录 验证角色和权限

💻 JWT工具类实现

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

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

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

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

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);
  }
}

🔄 代码执行逻辑

是 否 是 否 是 否 是 否 初始化配置 创建JwtRealm实例 创建DefaultSecurityManager实例 将JwtRealm传入SecurityManager 将SecurityManager绑定到SecurityUtils 获取当前用户Subject JWT令牌生成与验证 生成JWT令牌 验证JWT令牌有效性 从JWT令牌中提取用户名 创建JwtToken对象 JWT认证流程 使用JwtToken进行登录认证 登录成功? 记录登录成功日志 记录认证失败日志 JWT授权流程 检查用户是否具有admin角色 输出用户具有admin角色日志 输出用户不具有admin角色日志 检查用户是否具有user:delete权限 输出用户有删除用户的权限日志 输出用户没有删除用户的权限日志 检查用户是否具有product:create权限 输出用户有创建产品的权限日志 输出用户没有创建产品的权限日志 退出程序

🏆 学习成果检验

✅ 实践任务
  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的高级特性,并在实际项目中应用所学知识,不断提高自己的安全开发能力。

相关推荐
java_logo6 小时前
Transmission Docker 容器化部署指南
运维·docker·容器·kubernetes·apache·rocketmq·transmission
veteranJayBrother12 小时前
适配小程序的下滑上滑播放视频组件
小程序·apache·音视频
鸠摩智首席音效师13 小时前
Apache Prefork 和 Worker 有什么区别 ?
apache
鹿衔`2 天前
StarRocks 2.5.22 混合部署实战文档(CDH环境)
starrocks·apache·paimon
Rover.x2 天前
apache.poi XSSFWorkbook创建失败,空指针
apache
鹿衔`2 天前
Apache Doris 2.1.10 集群部署与 Paimon 数据湖集成实战文档
apache·doris·paimon
鹿衔`3 天前
Apache Doris 4.0.1 集群部署与 Paimon 数据湖集成实战文档
flink·apache·doris·paimon
SelectDB技术团队3 天前
面向 Agent 的高并发分析:Doris vs. Snowflake vs. ClickHouse
数据仓库·人工智能·科技·apache·知识图谱
初願致夕霞4 天前
C++文件压缩及解压缩小程序的实现
c++·小程序·apache