shiro权限控制原理及权限分隔符使用

AuthorizingRealm

AuthorizingRealm是shiro控制登录认证和鉴权的关键类。这个类集成了AuthenticatingRealm,提供了两个抽象方法:
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

通常这两个抽象方法需要我们自己去实现,也就是自定义Realm类,如:

public class UserRealm extends AuthorizingRealm

具体的实现方法网上有很多,这里就不贴代码了。重点介绍一下doGetAuthorizationInfo,该方法主要干的就一件事,就是把当前用户的角色和权限信息添加进AuthorizationInfo实例中,以便后续鉴权时使用。如下示例代码:

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        String accountId = JwtUtil.getClaim(principalCollection.toString(), CommonConstant.ACCOUNT);
        // 查询用户角色
        List<Role> roleLists = roleMapper.findRoleByAccountId(Integer.parseInt(accountId));
        for (Role role : roleLists) {
            if (role != null) {
                // 添加角色
                simpleAuthorizationInfo.addRole(role.getRoleName());
                // 根据用户角色查询权限
                List<Permission> permissions = permissionMapper.findSubPermissionByRole(role);
                for (Permission permission : permissions) {
                    if (permission != null) {
                        // 添加权限
                        simpleAuthorizationInfo.addStringPermission(permission.getPermissionCode());
                    }
                }
            }
        }
        return simpleAuthorizationInfo;
    }

鉴权原理

仍然还是在AuthorizingRealm类中有如下三个方法用来鉴权,最后一个方法属于具体的鉴权实现。第一个属于自定义PermissionResolver,这个本篇暂不做介绍。主要介绍第二个默认的鉴权处理方法。

    public boolean isPermitted(PrincipalCollection principals, String permission) {
        Permission p = getPermissionResolver().resolvePermission(permission);
        return isPermitted(principals, p);
    }

    public boolean isPermitted(PrincipalCollection principals, Permission permission) {
        AuthorizationInfo info = getAuthorizationInfo(principals);
        return isPermitted(permission, info);
    }

    //visibility changed from private to protected per SHIRO-332
    protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
        Collection<Permission> perms = getPermissions(info);
        if (perms != null && !perms.isEmpty()) {
            for (Permission perm : perms) {
                if (perm.implies(permission)) {
                    return true;
                }
            }
        }
        return false;
    }

从方法里边可以看出,首先就是根据登录用户身份从AuthorizationInfo中取出用户的角色权限信息,具体实现如下:

    //visibility changed from private to protected per SHIRO-332
    protected Collection<Permission> getPermissions(AuthorizationInfo info) {
        Set<Permission> permissions = new HashSet<Permission>();

        if (info != null) {
            Collection<Permission> perms = info.getObjectPermissions();
            if (!CollectionUtils.isEmpty(perms)) {
                permissions.addAll(perms);
            }
            perms = resolvePermissions(info.getStringPermissions());
            if (!CollectionUtils.isEmpty(perms)) {
                permissions.addAll(perms);
            }

            perms = resolveRolePermissions(info.getRoles());
            if (!CollectionUtils.isEmpty(perms)) {
                permissions.addAll(perms);
            }
        }

        if (permissions.isEmpty()) {
            return Collections.emptySet();
        } else {
            return Collections.unmodifiableSet(permissions);
        }
    }

    private Collection<Permission> resolvePermissions(Collection<String> stringPerms) {
        Collection<Permission> perms = Collections.emptySet();
        PermissionResolver resolver = getPermissionResolver();
        if (resolver != null && !CollectionUtils.isEmpty(stringPerms)) {
            perms = new LinkedHashSet<Permission>(stringPerms.size());
            for (String strPermission : stringPerms) {
                if (StringUtils.clean(strPermission) != null) {
                    Permission permission = resolver.resolvePermission(strPermission);
                    perms.add(permission);
                }
            }
        }
        return perms;
    }

关注一下代码最后的这两句:

Permission permission = resolver.resolvePermission(strPermission);
perms.add(permission);

第一句的resolver默认处理类是:

WildcardPermissionResolver

然后resolvePermission方法实现为:

    public Permission resolvePermission(String permissionString) {
        return new WildcardPermission(permissionString, caseSensitive);
    }

继续往源码里边走,发现如下实现方法:

   public WildcardPermission(String wildcardString, boolean caseSensitive) {
        setParts(wildcardString, caseSensitive);
    }


    protected void setParts(String wildcardString, boolean caseSensitive) {
        wildcardString = StringUtils.clean(wildcardString);

        if (wildcardString == null || wildcardString.isEmpty()) {
            throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.");
        }

        if (!caseSensitive) {
            wildcardString = wildcardString.toLowerCase();
        }

        List<String> parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));

        this.parts = new ArrayList<Set<String>>();
        for (String part : parts) {
            Set<String> subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN));

            if (subparts.isEmpty()) {
                throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted.");
            }
            this.parts.add(subparts);
        }

        if (this.parts.isEmpty()) {
            throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted.");
        }
    }

权限CODE分隔符

重点关注一下两个split方法,分隔符分别是冒号和逗号,这也就是为什么很多shiro定义的权限编码中间有冒号或者逗号的原因。暂且把冒号前定义的code为资源权限,逗号前定义的为操作权限。通常资源在前,操作在后。故源码也是先用冒号分隔,然后再用逗号分隔,最终把分隔完的字符放在

List<Set<String>>

集合里边备用。

protected static final String PART_DIVIDER_TOKEN = ":";
protected static final String SUBPART_DIVIDER_TOKEN = ",";

比如我们定义了一个权限的code为system_manager:user_manager:list,最后集合放的就是List的size为3,3个Set集合每个各一个元素。【system_manager      ,user_manager       ,list】这三个字符串。

比如我们定义了一个权限的code为system_manager,user_manager,list,最后集合放的就是List的size为1,1个Set集合3个元素。【system_manager      ,user_manager       ,list】这三个字符串。

两类分隔符实现的权限判断逻辑也有不同,具体可用文末的DEMO进行测试。

权限校验

回到前边截图的 这个方法protected boolean isPermitted(Permission permission, AuthorizationInfo info) 。前边讲了第二个参数用于获取用户拥有的权限并用分隔符分隔。第一个参数就是当我们请求一个接口时,给这个接口定义分访问权限。比如通过注解形式定义的:

@RequiresPermissions("system_manager:user_manager:list")

方法参数明白之后,继续看里边的实现方法:

for (Permission perm : perms) {
    if (perm.implies(permission)) {
        return true;
    }
}

首先for循环遍历用户拥有的所有权限。然后就是看用户拥有的权限与接口访问定义的权限是否匹配,匹配则证明有权限,可访问该接口。不匹配就表示无权访问。

权限校验规则

重点就是这个方法perm.implies(permission),实现源码如下:

    public boolean implies(Permission p) {
        // By default only supports comparisons with other WildcardPermissions
        if (!(p instanceof WildcardPermission)) {
            return false;
        }

        WildcardPermission wp = (WildcardPermission) p;

        List<Set<String>> otherParts = wp.getParts();

        int i = 0;
        for (Set<String> otherPart : otherParts) {
            // If this permission has less parts than the other permission, everything after the number of parts contained
            // in this permission is automatically implied, so return true
            if (getParts().size() - 1 < i) {
                return true;
            } else {
                Set<String> part = getParts().get(i);
                if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
                    return false;
                }
                i++;
            }
        }

        // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
        for (; i < getParts().size(); i++) {
            Set<String> part = getParts().get(i);
            if (!part.contains(WILDCARD_TOKEN)) {
                return false;
            }
        }

        return true;
    }

重点关注return结果的地方,第一个return true需要等第二个return false的结果,故先说

第二个return false。

看if条件就知道只有当用户权限不存在*号且用户权限code中有不包含的接口权限code则返回false无权限

这里有点绕,先需要说明一点就是进入这个方法是把用户拥有的每个权限code,都拿来遍历一下,然乎对比接口定义的权限code。然后不管是用户拥有的权限code或者接口定义的权限code都是被split形成一个Set<String>。

假设当前传递进方法的用户权限code为:system_manager:user_manager:create,而接口权限code为system_manager:user_manager:list,则回触发第二个return false。

怎么才会触发返回第一个return true呢?

看源码知道当用户权限code分隔后的长度小于接口定义访问权限的code就会触发。什么意思呢?就是当用户权限code长度小于等于接口定义权限code长度,则先走第二个return false的判断,如果始终没有被return false,则会触发return true。比如:

用户权限code定义为:system_manager:user_manager,而接口权限code为system_manager:user_manager:list,则回触发第一个return true。

第三个return false

当用户权限code中不存在*号则返回无权限。比如用户权限code定义为:system_manager:user_manager:list:check,而接口权限code为system_manager:user_manager:list,则会触发第三个return false。

当上边三个都没返回就返回第四个return true

权限CODE判断的工具类

下边为自己写的一个接口权限code和用户权限code比对的工具类,可以方便的让用户判断某些权限校验的异常场景,而不用在SHIRO源码里边DEBUG。

public class RealmTest {
    protected static final String WILDCARD_TOKEN = "*";
    protected static final String PART_DIVIDER_TOKEN = ":";
    protected static final String SUBPART_DIVIDER_TOKEN = ",";
    public static void main(String[] args) {
        System.out.println(implies());
    }

    public static boolean implies() {
        //定义接口权限code
        List<Set<String>> httpParts = setParts("system_manager:role_manager:list");
        //定义用户权限code
        String userParts = "system_manager:role_manager:list:check";
        int i = 0;
        for (Set<String> otherPart : httpParts) {
            // If this permission has less parts than the other permission, everything after the number of parts contained
            // in this permission is automatically implied, so return true
            if (getParts(userParts).size() - 1 < i) {
                System.out.println("返回第一个true");
                return true;
            } else {
                Set<String> part = getParts(userParts).get(i);
                if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
                    System.out.println("返回第二个fasle");
                    return false;
                }
                i++;
            }
        }

        // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
        for (; i < getParts(userParts).size(); i++) {
            Set<String> part = getParts(userParts).get(i);
            if (!part.contains(WILDCARD_TOKEN)) {
                System.out.println("返回第三个fasle");
                return false;
            }
        }
        System.out.println("返回第四个true");
        return true;
    }

    private static List<Set<String>> getParts(String wildcardString){
        /*Set<String> userCodeSet1 = new HashSet<>();
        Set<String> userCodeSet2 = new HashSet<>();
        Set<String> userCodeSet3 = new HashSet<>();
        Set<String> userCodeSet4 = new HashSet<>();
        userCodeSet1.add("system_manager");
        userCodeSet2.add("role_manager");
        userCodeSet3.add("list");
        userCodeSet4.add("aaa");

        List<Set<String>> parts = new ArrayList<>();
        parts.add(userCodeSet1);
        parts.add(userCodeSet2);
        parts.add(userCodeSet3);
        parts.add(userCodeSet4);*/
        return setParts(wildcardString);
    }


    protected  static List<Set<String>>  setParts(String wildcardString) {
        List<Set<String>> parts2 = new ArrayList<>();
        wildcardString = StringUtils.clean(wildcardString);

        if (wildcardString == null || wildcardString.isEmpty()) {
            throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.");
        }

        List<String> parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));

        parts2 = new ArrayList<Set<String>>();
        for (String part : parts) {
            Set<String> subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN));

            if (subparts.isEmpty()) {
                throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted.");
            }
            parts2.add(subparts);
        }

        if (parts2.isEmpty()) {
            throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted.");
        }
        return parts2;
    }
}

猜你喜欢

转载自blog.csdn.net/h363659487/article/details/131412223