1. 简介
本文将介绍如何使用 Apache Shiro 这个 Java 安全框架,实现细粒度的基于权限的访问控制(Permissions-Based Access Control)。
相比简单的角色判断,权限模型能让你的系统更灵活、更易维护。尤其是在业务频繁变更的项目中,避免“改个权限就得改代码、重新打包上线”这种踩坑操作。
2. 环境准备
我们沿用 Shiro 的基础配置,仅引入 shiro-core
模块即可:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
为了简化测试,我们使用一个 shiro.ini
文件作为 Realm 数据源,放在类路径根目录下:
[users]
jane.admin = password, admin
john.editor = password2, editor
zoe.author = password3, author
[roles]
admin = *
editor = articles:*
author = articles:create, articles:edit
接着初始化 Shiro:
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
SecurityManager securityManager = new DefaultSecurityManager(iniRealm);
SecurityUtils.setSecurityManager(securityManager);
这样就完成了最简安全环境搭建 ✅
3. 角色与权限
权限控制的核心是两个概念:用户(Subject)、角色(Role) 和 权限(Permission)。
角色是用户的一个分类标签,比如 admin
、editor
,代表一类用户的访问能力。传统做法是通过角色来判断访问权限:
Subject subject = SecurityUtils.getSubject();
if (subject.hasRole("admin")) {
logger.info("Welcome Admin");
}
⚠️ 但这种方式有个致命问题:把权限逻辑硬编码在代码里了。
比如,某天产品经理说:“所有编辑角色也能删除文章”。你得改代码里的 hasRole("admin")
判断,然后重新构建部署 —— 这显然不现实。
3.1. 权限(Permission)才是王道
Shiro 的设计哲学是:权限描述“能做什么”,而不是“谁可以做”。
权限是系统操作的抽象,比如:
articles:create
:创建文章profile:edit
:编辑个人资料document:approve
:审批文档
使用权限判断更灵活:
Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:create")) {
// 创建新文章
}
✅ 优势很明显:权限变更只需改配置,无需动代码。
❌ 注意:权限在 Shiro 中是可选项,但一旦用上,就别回头了。
3.2. 权限如何分配给用户
Shiro 的权限模型很灵活,支持:
- 权限 → 角色 → 用户
- 权限 → 用户(直接分配)
但大多数 Realm(包括我们用的 IniRealm
)只支持“权限绑定到角色”。
结构如下:
User (zoe.author)
└─ Role: author
└─ Permissions: articles:create, articles:edit
对应 shiro.ini
配置:
[users]
zoe.author = password3, author
[roles]
author = articles:create, articles:edit
其他 Realm(如 JdbcRealm
)也可以配置权限与角色的映射,原理一致。
4. 通配符权限(Wildcard Permissions)
Shiro 默认的权限实现是 Wildcard Permissions,它用字符串表达权限,并支持通配和层级结构,非常实用。
基本结构
权限字符串由多个部分组成,用冒号 :
分隔:
资源:操作:实例ID
例如:
articles:edit:123
可理解为:
articles
:资源类型edit
:操作类型123
:具体资源 ID
当然,组件数量不限于三个,但三段式最常见。
使用示例:
Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:edit:123")) {
// 编辑 ID 为 123 的文章
}
4.1. 权限推断与实例级控制
这是 Wildcard Permissions 的精髓:权限判断是“推断(implies)”而非“相等(equals)”。
- ❌ 角色判断:
hasRole("editor")
→ 必须精确匹配 - ✅ 权限判断:
isPermitted("articles:edit:1")
→ 只要用户权限能“推断出”该权限即可
举个例子,如果 author
角色拥有:
[roles]
author = articles:*
那么以下所有判断都为 true
:
subject.isPermitted("articles:create") → ✅
subject.isPermitted("articles:edit") → ✅
subject.isPermitted("articles:delete") → ✅
因为 articles:*
中的 *
是通配符,匹配任意第二级操作。
更进一步,还可以支持多级通配:
articles:*:* → 匹配所有文章的所有操作
*:edit:* → 匹配所有资源的编辑操作
性能建议
由于权限推断是模式匹配,比字符串相等更耗性能,所以:
✅ 推荐:使用最具体的权限判断
❌ 避免:过度依赖通配符做判断
// 推荐:具体判断
if (subject.isPermitted("articles:edit:1")) { ... }
// 不推荐:太宽泛,影响性能
if (subject.isPermitted("articles:*")) { ... }
5. 自定义权限实现
虽然 Wildcard Permissions 能覆盖大多数场景,但某些特殊业务可能需要自定义权限模型。
比如:路径权限,即 /articles
的权限应自动包含 /articles/drafts
下的所有子路径。
5.1. 实现 Permission 接口
只需实现 Permission
接口的 implies
方法:
public class PathPermission implements Permission {
private final Path path;
public PathPermission(Path path) {
this.path = path;
}
@Override
public boolean implies(Permission p) {
if (p instanceof PathPermission) {
return ((PathPermission) p).path.startsWith(path);
}
return false;
}
}
逻辑很简单:当前路径是否为被判断路径的前缀(即父路径)。
5.2. 注册自定义 PermissionResolver
Shiro 需要知道如何把字符串转成 PathPermission
对象,这由 PermissionResolver
负责:
public class PathPermissionResolver implements PermissionResolver {
@Override
public Permission resolvePermission(String permissionString) {
return new PathPermission(Paths.get(permissionString));
}
}
然后注入到 Realm 中:
IniRealm realm = new IniRealm();
Ini ini = Ini.fromResourcePath(Main.class.getResource("/shiro.ini").getPath());
realm.setIni(ini);
realm.setPermissionResolver(new PathPermissionResolver());
realm.init();
SecurityManager securityManager = new DefaultSecurityManager(realm);
SecurityUtils.setSecurityManager(securityManager);
更新 shiro.ini
配置为路径风格:
[roles]
admin = /
editor = /articles
author = /articles/drafts
使用方式:
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.isPermitted("/articles/drafts/new-article")) {
log.info("You can access articles");
}
结果:
admin
→ 可访问所有路径editor
→ 可访问/articles
及其子路径author
→ 仅可访问/articles/drafts
下内容
生产环境配置示例
实际项目中,通常用 shiro.ini
或 Spring 配置。例如:
[main]
permissionResolver = com.example.shiro.PathPermissionResolver
dataSource = org.apache.shiro.jndi.JndiObjectFactory
dataSource.resourceName = java://app/jdbc/myDataSource
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource = $dataSource
jdbcRealm.permissionResolver = $permissionResolver
这样就能在 JDBC Realm 中使用自定义权限模型了。
6. 总结
本文系统介绍了 Apache Shiro 的权限控制机制,重点包括:
- ✅ 使用
isPermitted()
替代hasRole()
,避免硬编码 - ✅ Wildcard Permissions 支持层级和通配,适合大多数场景
- ✅ 权限判断是“推断”而非“相等”,更灵活
- ✅ 可通过自定义
Permission
和PermissionResolver
实现复杂模型(如路径权限)
权限模型是安全系统的基石,用好 Shiro 的权限机制,能让你的系统在面对复杂授权需求时依然游刃有余。
示例代码已上传至 GitHub:https://github.com/baeldung/tutorials/tree/master/security-modules/apache-shiro