Shiro 学习笔记(5)—— 自定义角色权限解析器
执行授权代码的前提
我们首先要明确的一个知识点是,什么时候 Shiro 才会去执行权限匹配的代码呢?
(1)在我们的 Realm 的实现类中,必须要有关于 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)
方法的实现;
(2)在调用 Shiro 的认证成功以后的 isPermitted()
方法或者 checkPermission()
方法的时候。这一点很容易看出,当我们检查权限的时候,就会去执行授权的逻辑。
自定义权限解析规的步骤
步骤1:实现 Permission 接口
在这个接口中实现权限匹配的方法。
public class MyPermission implements Permission {
private String resourceId;
private String operator;
private String instanceId;
// 这里为了说明问题,省略了 get 和 set 方法
// 同时 get 和 set 方法只会在这个类的内部使用,所以其实没有必要设置和对外开放
// 调用方法也会消耗更多内存
public MyPermission(){
}
public MyPermission(String permissionStr){
String[] strs = permissionStr.split("\\+");
if(strs.length>1){
this.resourceId = strs[1];
}
if(this.resourceId == null || "".equals(this.resourceId)){
this.resourceId = "*";
}
if(strs.length>2){
this.operator = strs[2];
}
if(strs.length>3){
this.instanceId = strs[3];
}
if(this.instanceId == null || "".equals(this.instanceId)){
this.instanceId = "*";
}
System.out.println("实例化 MyPermission 时 => " + this.toString());
}
/**
* 【这是一个非常重要的方法】
* 由程序员自己编写授权是否匹配的逻辑,
* 我们这里的实现,是将 Realm 中给出的 Permission 和 ini 配置中指定的 PermissionResoler 中指定的 Permission 进行比对
* 比对的规则完全由我们自己定义
* @param permission
* @return
*/
@Override
public boolean implies(Permission permission) {
if(!(permission instanceof MyPermission)){
return false;
}
MyPermission mp = (MyPermission)permission;
if(!"*".equals(mp.resourceId) && !this.resourceId.equals(mp.resourceId)){
return false;
}
if(!"*".equals(mp.operator) && !this.operator.equals(mp.operator)){
return false;
}
if(!"*".equals(mp.instanceId) && !this.instanceId.equals(mp.instanceId)){
return false;
}
return true;
}
@Override
public String toString() {
return "MyPermission{" +
"resourceId='" + resourceId + '\'' +
", operator='" + operator + '\'' +
", instanceId='" + instanceId + '\'' +
'}';
}
}
步骤2:实现 PermissionResolver 接口
实现 PermissionResolver 接口的意义在于告诉 Shiro 根据字符串的表现形式(表现特征),采用什么样的 Permission 进行匹配。
代码:
public class MyPermissionResolver implements PermissionResolver{
@Override
public Permission resolvePermission(String s) {
if(s.startsWith("+")){
return new MyPermission(s);
}
return new WildcardPermission(s);
}
}
说明:这段代码实现的一个效果是:如果我们的权限字符串是以 “+” 号开头的话,就使用我们自定义的 MyPermission ,否则就使用默认的 WildcardPermission 解析这个字符串。
步骤3:实现授权的逻辑
这里,我们还是使用刚刚用的 StaticRealm 去实现授权操作,代码:
public class MyStaticRealm extends AuthorizingRealm {
/**
* 用于授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("static Realm 中授权的方法");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("r1");
info.addRole("r2");
info.addStringPermission("+user+");
info.addObjectPermission(new MyPermission("+user+add+1"));
return info;
}
/**
* 用于认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 省略,同之前的例子
}
}
步骤 4:在 shiro.ini 文件中增加配置
permissionResolver=com.liwei.permission.MyPermissionResolver
authorizer.permissionResolver=$permissionResolver
securityManager.authorizer=$authorizer
详细说明代码执行流程
代码其实就是上面的样子,但是光这么讲很抽象,此时权限匹配的过程是定义在我们实现的 Permission 接口的 implies() 方法中。所以我们还要详细介绍 Shiro 在进行权限匹配的流程:
1、首先调用 Subject.isPermitted()
接口,其会委托给 SecurityManager
,而 SecurityManager
接着会委托给 Authorizer
;
2、Authorizer
是真正的授权者,
如果我们调用如 isPermitted(“user:view”),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例。下面我们说明这个实例是如何转换的呢?
根据我们在 shiro.ini 文件中配置的
permissionResolver=com.liwei.permission.MyPermissionResolver
authorizer.permissionResolver=$permissionResolver
securityManager.authorizer=$authorizer
此时 Shiro 会去这个配置文件中指定的 PermissionResolver
找相应的实现类 MyPermissionResolver ,通过“user:view”这个字符串去实现类中找到对应应该使用的 Permission 实现类,然后实例化一个 Permission 对象(这个 Permission 对象很重要,我们暂时把它记住,下一步马上就要用来做匹配)。
3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;
这个转换实例的过程就是在代码中通过在 Realm
中实现的 doGetAuthorizationInfo()
方法里写的 info.addObjectPermission(new MyPermission(“+user+add+1”)); 即构造函数实现的,在这个过程中也实例化了一个 Permission 对象,用于和上一步的 Permission 对象进行比较,这个比较的过程是通过我们实现 Permission
接口中的 implies()
方法来定义的 。
如果权限的字符串表示是一个集合,那么 implies()
方法可能会被执行多次,直到该方法返回 ‘true’ 为止。
4、Authorizer
会判断 Realm
的角色/权限是否和传入的匹配,如果有多个 Realm
,会委托给 ModularRealmAuthorizer
进行循环判断,如果匹配,那么 isPermitted*/hasRole*
会返回 true,否则返回 false ,表示授权验证失败。
自定义角色匹配器
下面继续说明如何自定义角色匹配,其实过程和自定义权限匹配是类似的,但是我们要明确一点,因为角色是一组权限的集合,所以我们指定角色解析器的时候,须要告诉 Shiro 使用什么 Permission 。
步骤1:实现 RolePermissionResolver 接口
public class MyRolePermissionResolver implements RolePermissionResolver {
@Override
public Collection<Permission> resolvePermissionsInRole(String s) {
if(s.contains("role1")){
return Arrays.asList((Permission) new MyPermission("+user+add+1"));
}
return null;
}
}
步骤2:在 shiro.ini 中配置我们的 RolePermissionResolver
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
rolePermissionResolver=com.liwei.role.MyRolePermissionResolver
authorizer.rolePermissionResolver=$rolePermissionResolver
permissionResolver=com.liwei.permission.MyPermissionResolver
authorizer.permissionResolver=$permissionResolver
securityManager.authorizer=$authorizer
securityManager.realms=$myStaticRealm
步骤3:在自定义的 Realm 的授权方法中添加角色
public class MyStaticRealm extends AuthorizingRealm {
/**
* 用于授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("static Realm 中授权的方法");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("role1");
return info;
}
/**
* 用于认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 省略
}
}
步骤 4 :判断用户是否具有授权方法中指定角色的权限
代码片段:
currentSubject.checkPermission("+user+add+1");
(终于写完了,不知道能不能讲清楚这件事情,大家光看也是很难理解的,须要自己动手练习,多打出一些帮助理解的代码信息就可以很快理解了)。