Apache Shiro是一个强大而直观的Java安全框架,为认证、授权、加密和会话管理提供了一套简洁的API 。它通过清晰的架构设计,将复杂的安全逻辑抽象为几个核心组件,让开发者能够轻松地为应用程序添加身份验证和访问控制功能。
Shiro的设计哲学是"简单而强大",它不依赖于任何容器,可以在任何Java环境中运行,从简单的命令行应用到大型企业级Web应用都能胜任。与Spring Security相比,Shiro的学习曲线更加平缓,API设计更加直观,这使得它成为许多Java开发者的首选安全框架。
1、Shiro核心组件
Subject(主体)
应用代码直接交互的对象是Subject ,也就是说Shiro的对外API核心就是Subject。Subject代表了当前"用户",这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫、机器人等。与Subject的所有交互都会委托给SecurityManager,Subject其实是一个门面,SecurityManager才是实际的执行者;
SecurityManager(安全管理器)
即安全管理器 ;所有与安全有关的操作都会与SecurityManager交互,且其管理着所有Subject。可以看出它是Shiro的核心 ,它负责与Shiro的其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色。
Realm(域)
连接Shiro与应用程序安全数据的桥梁。负责从数据源(如数据库、LDAP)获取认证和授权信息。
Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法。也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作。可以把Realm看成DataSource。
2、Shiro 内部架构

***
Subject:*任何可以与应用交互的"用户"。
SecurityManager:Shiro 的核心,所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证、授权、会话及缓存的管理。
Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下判定用户认证通过。
Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有 1 个或多个 Realm,可以认为为安全实体数据源,用于获取安全实体;可以是 JDBC 实现,也可以是内存实现等等;由用户提供,所以一般在应用中都需要实现自己的 Realm;
SessionManager:管理Session声明周期的组件,通过Shiro框架的这一模块,开发者可在任何应用或架构层一致地使用Session API;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能;
Cryptography:密码模块,提供了一些常见的加密组件用于如密码加密/解密。
3、核心概念:身份与凭证
在Shiro的世界里,一切安全操作都围绕主体(Subject) 展开,它代表当前与应用交互的"用户"。每个主体都通过身份(Principals) 和**凭证(Credentials)**来证明自己是谁。
身份(Principals)
主体的标识属性,可以是用户名、邮箱、用户ID等任何能够唯一标识用户的属性。Shiro支持多个身份,但通常会指定一个**主要身份(Primary Principal)**作为应用内的唯一标识。
凭证(Credentials)
证明身份所有权的秘密信息,最常见的组合就是用户名/密码。其他凭证类型还包括生物特征(指纹、视网膜扫描)和数字证书等。
4、认证流程:三步验证身份
认证是验证用户身份的过程,Shiro将其抽象为清晰的三个步骤,确保用户身份的合法性。
当用户调用subject.login(token)时,背后发生了复杂的交互过程:
-
第一步:委托认证 - Subject将认证请求委托给SecurityManager
-
第二步:策略执行 - SecurityManager委托给Authenticator,根据配置的认证策略调用Realm
-
第三步:数据验证 - Realm检查提交的token,从数据源验证用户身份
-
第四步:状态管理 - 认证成功后,用户信息存储在Subject的会话中
java
// Shiro认证核心代码示例
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
token.setRememberMe(true);
try {
currentUser.login(token);
System.out.println("登录成功!");
} catch (UnknownAccountException uae) {
System.out.println("用户名不存在!");
} catch (IncorrectCredentialsException ice) {
System.out.println("密码错误!");
} catch (LockedAccountException lae) {
System.out.println("账户已锁定!");
} catch (AuthenticationException ae) {
System.out.println("认证失败: " + ae.getMessage());
}
5、认证架构:组件协同工作
当调用Subject.login()时,Shiro内部会启动一个精密的认证序列,多个组件协同完成身份验证。
认证组件详解
SecurityManager
安全管理的核心,作为"保护伞"组件接收认证请求并委托给Authenticator处理。
Authenticator
认证管理器,默认使用
ModularRealmAuthenticator,支持单Realm或多Realm认证策略。Realm
安全数据源,负责从数据库、LDAP等存储中获取用户身份和凭证信息进行比对。
AuthenticationStrategy
认证策略,在多Realm环境下协调认证过程,决定整体认证成功条件。
6、授权机制:精细化的访问控制
授权是控制"用户能做什么"的过程,Shiro提供了三种灵活的授权方式,满足不同场景的需求。
三种授权方式对比
| 授权方式 | 实现机制 | 适用场景 |
|---|---|---|
| 基于角色 | 检查用户是否拥有特定角色 | 简单的角色控制场景 |
| 基于权限 | 检查用户是否拥有特定权限 | 精细化的权限控制 |
| 基于注解 | 在方法上使用注解声明权限 | Spring集成场景 |
Shiro使用权限字符串来表示权限,格式为资源类型:操作:资源实例ID,支持通配符匹配,提供了极大的灵活性。
java
// 授权检查API示例
Subject currentUser = SecurityUtils.getSubject();
// 基于角色的访问控制
if (currentUser.hasRole("admin")) {
// 有admin角色
}
// 基于权限的访问控制
if (currentUser.isPermitted("user:create")) {
// 有创建用户的权限
}
// 检查多个权限
boolean[] permitted = currentUser.isPermitted("user:create", "user:delete");
// 基于实例的权限检查
if (currentUser.isPermitted("user:update:1")) {
// 有更新ID为1的用户的权限
}
7、会话管理:容器无关的会话控制
Shiro提供了完整的企业级会话管理功能,最大的特点是容器无关性------可以在任何环境使用相同的API,不依赖于Web容器的session实现。
会话管理的核心特性包括:
- 统一API - 使用类似HttpSession的API,学习成本低
- 多种存储 - 支持内存、文件、数据库、Redis等多种存储方式
- 集群支持 - 可配置分布式会话管理,适合微服务架构
- 会话监听 - 提供会话监听器,监控会话生命周期事件
8、安全漏洞与防御:RememberMe反序列化漏洞
Shiro历史上曾存在著名的RememberMe反序列化漏洞(CVE-2016-4437),这是一个高危的远程代码执行漏洞,CVSS评分高达9.8分。
漏洞的核心问题在于两点:首先,Shiro早期版本使用了硬编码的默认AES密钥,攻击者可以直接获取;其次,解密后的数据直接进行反序列化操作,缺乏必要的安全校验。
漏洞修复方案
1. 根本性修复
升级Shiro版本并配置强密钥。AES-128至少16字节,AES-192至少24字节,AES-256至少32字节。
2. 临时缓解
禁用RememberMe功能、实施反序列化过滤、替换反序列化组件为Jackson等更安全的方案。
3. 纵深防御
代码层面避免漏洞依赖、网络层面配置WAF规则、运维层面建立配置审计流程。
