Apache Shiro 教程

Apache Shiro 教程

Apache Shiro 完整教程

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


📖 学习路径导航

章节结构

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

🎯 教程核心价值

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

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


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

🎯 学习目标

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

🔍 什么是Apache Shiro?

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

📚 设计理念

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

🌟 Shiro核心优势

  1. 简洁直观的API设计

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

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

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

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

🏗️ 核心架构

mermaid
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

🔄 核心组件交互流程

mermaid
sequenceDiagram
    participant 应用程序
    participant Subject
    participant SecurityManager
    participant Authenticator
    participant Authorizer
    participant Realm
    participant 数据源

    应用程序->>Subject: 创建认证请求
    Subject->>SecurityManager: 委托认证处理
    SecurityManager->>Authenticator: 执行认证
    Authenticator->>Realm: 查询用户信息
    Realm->>数据源: 获取用户凭证
    数据源-->>Realm: 返回用户数据
    Realm-->>Authenticator: 返回AuthenticationInfo
    Authenticator-->>SecurityManager: 认证结果
    SecurityManager-->>Subject: 认证成功/失败
    Subject-->>应用程序: 返回认证状态

    应用程序->>Subject: 请求授权检查
    Subject->>SecurityManager: 委托授权处理
    SecurityManager->>Authorizer: 执行授权检查
    Authorizer->>Realm: 查询角色权限
    Realm->>数据源: 获取权限数据
    数据源-->>Realm: 返回权限数据
    Realm-->>Authorizer: 返回AuthorizationInfo
    Authorizer-->>SecurityManager: 授权结果
    SecurityManager-->>Subject: 授权成功/失败
    Subject-->>应用程序: 返回授权状态

    Note over SecurityManager,SessionManager: 同时管理会话和加密操作

🎭 核心组件详解

组件角色核心职责代码示例
Subject🎭 当前用户代表与系统交互的实体SecurityUtils.getSubject()
SecurityManager🏰 安全管家协调所有安全操作new DefaultSecurityManager()
Realm🗄️ 数据桥梁连接安全数据源new IniRealm("classpath:shiro.ini")
Authenticator🔍 身份验证官验证用户身份ModularRealmAuthenticator
Authorizer⚖️ 权限审核官检查用户权限ModularRealmAuthorizer
SessionManager📋 会话管理员管理用户会话DefaultSessionManager
CacheManager⚡ 性能加速器缓存安全数据MemoryConstrainedCacheManager

🔍 Shiro 与 Spring Security 对比

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

选择建议

选择Shiro的场景

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

选择Spring Security的场景

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

🏆 学习成果检验

✅ 基础理解

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

第二章:身份认证(Authentication)

🎯 学习目标

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

🔐 认证流程详解

认证架构设计

mermaid
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

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

🔄 代码执行逻辑

mermaid
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

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

🏆 学习成果检验

✅ 实践任务

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

第三章:角色管理(Roles)

🎯 学习目标

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

🧩 角色授权模型

RBAC模型深度解析

mermaid
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

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

🔄 代码执行逻辑

mermaid
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

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

# 角色权限配置部分,用于定义角色所拥有的权限
[roles]
# 格式: 角色名 = 权限
# 示例配置了"role"角色拥有"permission"权限
role = permission

🏆 学习成果检验

✅ 实践任务

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

第四章:权限控制(Permissions)

🎯 学习目标

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

🎮 权限表达式语法详解

权限格式定义

text
权限格式:资源:操作:实例

示例:
- 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);
  }
}

🔄 代码执行逻辑

mermaid
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

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

🔄 代码执行逻辑

mermaid
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

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

🔄 代码执行逻辑

mermaid
flowchart TD
    subgraph "初始化配置"
        A[定义原始密码]
        A --> B[使用MD5算法加密]
        A --> C[使用SHA-256算法加密]
        A --> D[使用带盐值的SHA-256加密]
        B --> E[输出MD5加密结果]
        C --> F[输出SHA-256加密结果]
        D --> G[输出带盐值SHA-256加密结果]
    end

    subgraph "密码验证流程"
        H[使用正确密码验证]
        H --> I{验证成功?}
        I -- 是 --> J[输出验证成功日志]
        I -- 否 --> K[输出验证失败日志]

        L[使用错误密码验证]
        L --> M{验证成功?}
        M -- 是 --> N[输出错误密码验证成功日志]
        M -- 否 --> O[输出错误密码验证失败日志]
    end

    J --> P[退出程序]
    K --> P
    N --> P
    O --> P

🏆 学习成果检验

✅ 实践任务

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

第七章:自定义Realm

🎯 学习目标

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

🗄️ 自定义Realm概述

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

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

💻 代码示例

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

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

  }
}

🔄 代码执行逻辑

mermaid
flowchart TD
    subgraph "初始化配置"
        A[初始化配置] --> B[创建自定义UserRealm实例]
        B --> C[创建DefaultSecurityManager实例]
        C --> D[将UserRealm传入SecurityManager]
        D --> E[将SecurityManager绑定到SecurityUtils]
    end

    subgraph "认证流程"
        F[认证流程] --> G[获取当前用户Subject]
        G --> H{检查用户是否已认证}
        H -- 是 --> J[跳过认证]
        H -- 否 --> I[创建UsernamePasswordToken]
        I --> K[执行登录认证]
        K --> L{登录成功?}
        L -- 是 --> M[记录登录成功日志]
        L -- 否 --> N[记录认证失败日志]
    end

    subgraph "授权流程"
        O[授权流程]
        O --> P[检查用户是否具有admin角色]
        P -- 是 --> Q[记录角色检查通过日志]
        P -- 否 --> R[记录角色检查失败日志]
        Q --> S[检查用户是否具有user:delete权限]
        R --> S
        S -- 是 --> T[记录权限检查通过日志]
        S -- 否 --> U[记录权限检查失败日志]
        T --> V[检查用户是否具有product:create权限]
        U --> V
        V -- 是 --> W[记录权限检查通过日志]
        V -- 否 --> X[记录权限检查失败日志]
    end

    W --> Y[退出程序]
    X --> Y

🏆 学习成果检验

✅ 实践任务

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

第八章:缓存支持(Cache)

🎯 学习目标

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

📦 缓存概述

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

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

🔧 项目缓存实现

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

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

🔄 代码执行逻辑

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

📦 缓存工作原理

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

mermaid
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

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

🔄 代码执行逻辑

mermaid
flowchart TD
    subgraph "初始化配置"
        A[初始化配置] --> B[创建JwtRealm实例]
        B --> C[创建DefaultSecurityManager实例]
        C --> D[将JwtRealm传入SecurityManager]
        D --> E[将SecurityManager绑定到SecurityUtils]
        E --> F[获取当前用户Subject]
    end

    subgraph "JWT令牌生成与验证"
        G[JWT令牌生成与验证] --> H[生成JWT令牌]
        H --> I[验证JWT令牌有效性]
        I --> J[从JWT令牌中提取用户名]
        J --> K[创建JwtToken对象]
    end

    subgraph "JWT认证流程"
        L[JWT认证流程] --> M[使用JwtToken进行登录认证]
        M --> N{登录成功?}
        N -- 是 --> O[记录登录成功日志]
        N -- 否 --> P[记录认证失败日志]
    end

    subgraph "JWT授权流程"
        Q[JWT授权流程] --> R[检查用户是否具有admin角色]
        R -- 是 --> S[输出用户具有admin角色日志]
        R -- 否 --> T[输出用户不具有admin角色日志]
        S --> U[检查用户是否具有user:delete权限]
        T --> U
        U -- 是 --> V[输出用户有删除用户的权限日志]
        U -- 否 --> W[输出用户没有删除用户的权限日志]
        V --> X[检查用户是否具有product:create权限]
        W --> X
        X -- 是 --> Y[输出用户有创建产品的权限日志]
        X -- 否 --> Z[输出用户没有创建产品的权限日志]
    end

    Y --> AA[退出程序]
    Z --> AA

🏆 学习成果检验

✅ 实践任务

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

📚 总结与进阶

🎯 核心知识点回顾

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

🚀 进阶学习建议

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

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

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

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