Java 安全 | 从 Shiro 底层源码看 Shiro 漏洞 (上)
字数 1884 2025-08-20 18:17:59
Apache Shiro 安全框架深度解析与漏洞原理
一、Shiro 框架概述
1.1 权限管理基础概念
权限管理是指根据不同身份的用户登录系统后,展示不同功能模块的能力。例如:
- 学生登录:选课、成绩查询、课程表
- 老师登录:学生管理、成绩录入
1.2 权限管理实现方式
1.2.1 基于页面的实现
- 适用于权限管理简单、用户少的场景
- 缺点:新增功能需要修改页面,维护成本高
1.2.2 RBAC (基于角色的访问控制)
- 基本原型:用户-权限直接关联
- 改进版:引入角色概念,用户-角色-权限
- 高级版:用户-角色-权限 + 用户-权限(直接分配)
1.3 安全框架的作用
安全框架(如Shiro)相当于系统的"保安",负责:
- 认证(Authentication):验证用户身份
- 授权(Authorization):检查用户权限
- 会话管理(Session Management)
- 密码管理(Cryptography)
二、Shiro 核心功能与组件
2.1 核心功能
- 认证:验证用户身份(登录认证)
- 授权:检查用户权限/角色
- 会话管理:用户认证后创建会话
- 密码管理:敏感信息加密
2.2 支持特性
- Web支持:通过过滤器拦截请求
- 缓存支持:缓存用户信息和权限
- 并发支持
- 测试支持
- Remember Me功能
2.3 核心组件与运行流程
Subject (用户) → SecurityManager (安全管理器) → Realm (数据源)
- Subject:代表当前用户,由SecurityUtils创建
- SecurityManager:Shiro核心,管理所有Subject
- Realm:安全数据源,连接Shiro与应用的"桥梁"
三、Shiro 基础使用
3.1 Java SE 环境使用
3.1.1 依赖配置
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
3.1.2 IniRealm 配置
创建shiro.ini文件:
[users]
heihuUser=heihuPass,seller
hacker=123456,ckmgr
admin=admin888,admin
[roles]
seller=order-add,order-del,order-list
ckmgr=ck-add,ck-del,ck-list
admin=*
3.1.3 认证代码示例
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password));
System.out.println("登陆成功!");
} catch (IncorrectCredentialsException e) {
System.out.println("登陆失败!");
} catch (UnknownAccountException e) {
System.out.println("用户名不存在!");
}
3.1.4 授权检查
subject.hasRole("seller"); // 检查角色
subject.isPermitted("order-del"); // 检查权限
3.2 SpringBoot 整合 Shiro
3.2.1 依赖配置
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!-- 其他SpringBoot相关依赖 -->
3.2.2 Shiro 配置类
@Configuration
public class ShiroAutoConfiguration {
@Bean
public IniRealm getIniRealm() {
return new IniRealm("classpath:shiro.ini");
}
@Bean
public SecurityManager getSecurityManager(IniRealm iniRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(iniRealm);
return manager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean();
factory.setSecurityManager(securityManager);
Map<String,String> filterMap = new HashMap();
filterMap.put("/", "anon");
filterMap.put("/login", "anon");
filterMap.put("/user/login", "anon");
filterMap.put(authc");
factory.setFilterChainDefinitionMap(filterMap);
factory.setLoginUrl("/login");
factory.setUnauthorizedUrl(
return factory;
}
}
3.2.3 控制器示例
@Controller
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public String login(String username, String password) {
try {
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken(username, password));
return "index";
} catch (Exception e) {
return "login";
}
}
}
3.3 JdbcRealm 使用
3.3.1 数据库表结构
CREATE TABLE `users`(
username varchar(60) primary key,
password varchar(60) not null
);
CREATE TABLE `user_roles`(
username varchar(60) not null,
role_name varchar(100) not null
);
CREATE TABLE `roles_permissions`(
role_name varchar(100) not null,
permission varchar(100) not null
);
3.3.2 JdbcRealm 配置
@Bean
public JdbcRealm getJdbcRealm(DataSource dataSource) {
JdbcRealm jdbcRealm = new JdbcRealm();
jdbcRealm.setDataSource(dataSource);
jdbcRealm.setPermissionsLookupEnabled(true); // 开启授权功能
return jdbcRealm;
}
3.4 Shiro 标签使用
3.4.1 依赖配置
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
3.4.2 配置 ShiroDialect
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
3.4.3 模板中使用
<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<!-- 游客访问 -->
<shiro:guest>
欢迎游客访问 | <a href="/login">登录</a>
</shiro:guest>
<!-- 已登录用户 -->
<shiro:user>
欢迎用户名为: <shiro:principal/> 的用户访问!
<!-- 角色检查 -->
<shiro:hasRole name="admin">超级管理员</shiro:hasRole>
<!-- 权限检查 -->
<shiro:hasPermission name="order:add">
<a href="#">添加订单</a>
</shiro:hasPermission>
</shiro:user>
四、自定义 Realm 实现
4.1 数据库设计
4.1.1 表结构
-- 用户表
CREATE TABLE `tb_users`(
user_id int primary key,
username varchar(60) unique,
password varchar(60)
);
-- 角色表
CREATE TABLE `tb_roles`(
role_id int primary key,
role_name varchar(60)
);
-- 权限表
CREATE TABLE `tb_permissions`(
permission_id int primary key,
permission_code varchar(60),
permission_name varchar(60)
);
-- 用户角色关联表
CREATE TABLE `tb_urs`(
uid int,
rid int
);
-- 角色权限关联表
CREATE TABLE `tb_rps`(
rid int,
pid int
);
4.1.2 查询语句设计
- 认证查询:根据用户名查询用户信息
- 授权查询:
- 查询用户角色
- 查询角色权限
4.2 自定义 Realm 实现
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 查询用户角色
Set<String> roles = userService.findRolesByUsername(username);
info.setRoles(roles);
// 查询用户权限
Set<String> permissions = userService.findPermissionsByUsername(username);
info.setStringPermissions(permissions);
return info;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
String username = (String)token.getPrincipal();
User user = userService.findByUsername(username);
if(user == null) {
throw new UnknownAccountException("用户不存在");
}
return new SimpleAuthenticationInfo(
user.getUsername(),
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), // 加盐
getName()
);
}
}
4.3 密码加密与加盐
// 配置密码匹配器
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("SHA-256"); // 加密算法
matcher.setHashIterations(1024); // 哈希迭代次数
matcher.setStoredCredentialsHexEncoded(true); // 十六进制编码
return matcher;
}
// 在Realm中设置
@Bean
public CustomRealm customRealm() {
CustomRealm realm = new CustomRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
五、Shiro 漏洞原理分析
5.1 Shiro 记住我功能
Shiro的RememberMe功能实现机制:
- 用户登录时选择"记住我"
- 服务端生成包含用户信息的加密Cookie
- 下次访问时,Shiro自动解密Cookie进行认证
5.2 漏洞成因
5.2.1 加密密钥硬编码
早期版本使用固定加密密钥(硬编码在代码中)
5.2.2 AES加密模式问题
- 使用ECB模式(电子密码本模式)
- 相同明文块加密后得到相同密文块
- 无初始向量(IV),易受攻击
5.2.3 加密数据构造
RememberMe Cookie结构:
Base64(序列化数据 + AES加密(序列化数据))
5.3 攻击流程
- 构造恶意序列化数据
- 使用已知密钥或暴力破解密钥加密
- 替换Cookie中的RememberMe字段
- 服务端反序列化时执行恶意代码
5.4 漏洞修复方案
- 升级到最新版本
- 自定义加密密钥:
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(customRealm());
// 设置RememberMe管理器
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
rememberMeManager.setCipherKey(Base64.decode("自定义Base64编码密钥"));
manager.setRememberMeManager(rememberMeManager);
return manager;
}
- 禁用RememberMe功能(如不需要)
- 使用更安全的加密模式(如GCM)
六、Shiro 安全最佳实践
-
密钥管理:
- 避免使用默认密钥
- 定期更换密钥
- 密钥存储在安全位置
-
会话管理:
- 设置合理的会话超时时间
- 实现会话固定保护
- 限制并发会话数
-
密码安全:
- 使用强哈希算法(如SHA-256、BCrypt)
- 加盐处理
- 适当设置哈希迭代次数
-
权限设计:
- 遵循最小权限原则
- 定期审查权限分配
- 实现权限变更审计
-
其他安全措施:
- 启用CSRF防护
- 实现请求频率限制
- 记录安全相关事件
七、总结
Apache Shiro是一个功能强大且灵活的安全框架,通过本文我们深入了解了:
- Shiro的核心架构和运行原理
- 多种集成方式(Java SE、SpringBoot)
- 不同Realm的实现(Ini、JDBC、自定义)
- 权限控制和标签使用
- 安全漏洞的底层原理
- 安全加固的最佳实践
正确配置和使用Shiro可以显著提高应用的安全性,而不当的配置则可能引入严重的安全隐患。开发者应当深入理解其工作原理,遵循安全最佳实践,才能充分发挥Shiro的安全防护能力。