本文参考张开套的《跟我学 Shiro》
文章目录
什么是授权
授权也称作访问控制,即在应用中控制谁能够访问哪些资源(例如访问页面/编辑数据/页面操作等)。在授权中需要了解的几个关键对象:主体 Subject、资源 Resource、权限 Permission、角色 Role。
- Subject:主体,就是访问应用的用户,在 Shiro 中使用 Subject 代表用户,用户只有授权后才允许访问相应的资源。
- Resource:在应用中用户可以访问的任何东西都称作资源,比如查看页面/编辑页面数据都是资源,用户只有授权后才能访问。
- Permission:安全策略中的原子授权单位,简单来说就是在应用中用户能不能访问某个资源。
- Role:角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,这样用户可以拥有一组权限,赋予权限比较方方便,例如:项目经理、高级工程师、中级工程师都是角色,不同的角色拥有一组不同的权限。
- 隐式角色:直接通过角色来验证用户有没有操作权限。
- 显示角色:在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合,这样假设哪个角色不能访问某个资源,只需要从角色代表的权限集合中移除即可;无需修改多多处代码;粒度是以资源/实例为单位划分的,粒度较细。
授权方式
Shiro 支持三种方式的授权:
- Java代码实现
Subject subject = SecurityUtils.getSubject();
if (subject.hasRole("admin")){
// 具备 admin 权限
} else {
// 不具备
}
- 注解方式实现【没有权限将抛出相应的异常】
@RequiresRoles("admin")
public void hello() {
//有权限
}
- 通过 JSP/GSP 页面中相应的标签完成授权:
<shiro:hasRole name="admin">
<!-- 有权限 -->
</shiro:hasRole>
基于角色的访问控制
Shiro 提供了 hasRole/hasAllRoles/hasRoles 用于判断用户是否拥有某个角色/某些权限;
Shiro 提供的 checkRole/checkRoles 用于判断用户没有某个角色/权限则会抛出 UnauthorizedException 异常。
这种方式的缺点就是如果很多地方进行了角色判断,但是有一天不需要了那么就需要修改相应的代码把所有相关的地方进行删除,这就是粗粒度造成的问题。
基于资源的访问控制
Shiro 提供了 isPermitted和isPermittedAll 用于判断用户是否拥有某个权限或者所有权限,也没有提供如 isPermittedAny 用于判断拥有某一个权限的接口。
这种方式的一般规则是“资源标识符:操作”,即是资源级别的粒度;这种方式的好处就是如果要修改基本都是一个资源级别的修改,不会对其他模块产生影响,力度小。但是实现起来稍微复杂点,需要维护"用户——角色",“角色——权限”之间的关系。
Permission 字符串通配符权限
规则:资源标识符:操作:对象实例ID,即对哪个资源的实例可以进行什么操作。默认支持通配符权限字符串。
”:“ :表示资源/操作/实例的分割。
”,“ :逗号表示操作的分割。
”*“ :星号表示任意资源/操作/实例
1. 单个资源单个权限
subject().checkPermission("system:user:update");
2. 单个资源多个权限
ini 配置文件
role4=system:user:update,system:user:delete
Java 代码
subject().checkPermissions("system:user:update","system:user:delete")
上面的方式都可以简写:
role4="system:user:update,delete"
subject().checkPermission("system:user:update,delete");
3. 单个资源全部权限
ini 配置
role5="system:user:create,update,delete,view"
java 代码
subject().checkPermission("system:user:create,update,delete,view");
用户拥有增删改查的权限,可以简写为下面这样:
role5=system:user:*
subject().checkPermission("system:user:*");
4. 所有资源全部权限
role6=*:view
subject().checkPermission("user:view");
用户拥有所有资源的 view 所有权限,假设判断的权限是 system:user:view,可以这样写:
role5=*:*:view
授权流程
- 首先调用 Subject.isPermitted 的 hasRole 接口,将其委托给 SecurityManager,而 SecurityManager 接着会委托给 Authorizer;
- Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例;
- 在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;
- Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个 Realm ,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted*/hasRole* 会返回 true,否则返回 false 表示授权失败。
ModularRealmAuthorizer 进行多 Realm 匹配流程:
- 首先检查相应的 Realm 是否实现了 Authorizer;
- 如果实现了 Authorizer ,那么接着调用其相应的 isPermitted*/hasRole* 接口进行匹配;
- 如果有一个 Realm 匹配那么将返回 true,否则返回 false。
如果 Realm 进行授权的话,应该继承 AuthorizingRealm ,它的流程是:
- 如果调用 hasRole*,则直接获取 AuthorizationInfo.getRoles() 与传入的角色比较。*
- 首先如果调用如isPermitted(“user:view”),首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission;
- 如isPermitted(“user:view”),首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission;
- 接着调用Permission. implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则false。