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

这篇具有很好参考价值的文章主要介绍了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。文章来源地址https://www.toymoban.com/news/detail-511776.html

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;
    }
}

到了这里,关于shiro权限控制原理及权限分隔符使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Hive建表时,指定分隔符

     一.建表语句 二.语句分析 ROW FORMAT DELIMITED FIELDS TERMINATED BY \\\',\\\' : 这个子句表明Hive将使用 \\\',\\\' 字符作为列分隔符,即分隔字段。 ROW FORMAT DELIMITED COLLECTION items terminated BY \\\'_\\\'  :这个子句表明Hive将使用 \\\'_\\\' 作为集合元素间的分隔符。 ROW FORMAT DELIMITED MAP KEYS TERMINATED BY \\\':\\\' :这个

    2024年02月11日
    浏览(41)
  • 力扣2788-按分隔符拆分字符串

    题目链接 解题思路: 1 .传参是一个字符串数组,我们需要对每一个字符串处理 2 .解题中e是字符串数组中的每一个字符串 3 .i是每个字符串的下标,n为每个字符串的大小 4 .遍历整个字符串 5 .start是要切割的位置

    2024年01月20日
    浏览(45)
  • word的分隔符和分页符

    分隔符和分页符在Word中都是用于文本分段的工具,但它们的用途和操作方式有所不同。 分隔符是一个广义的概念,包括分页符、分节符和分栏符等。它是一种统称,可以将一个完整的文本内容分成两个或更多的部分。在将表格转换为文本时,分隔符用于标识文字分隔的位置

    2024年02月08日
    浏览(41)
  • Leetcode 2788. 按分隔符拆分字符串

    我们可以先自己模拟一下分隔字符串的过程。如果只是简单的,遇到分隔符,将分隔符前后的子串加入结果的List,那么很显然并没有考虑到一个String中有多个字符串的情况。一种比较容易想到的方法是: 先对List中每个字符串遍历; 对于每个字符串内部,维护一个缓冲区;

    2024年01月21日
    浏览(46)
  • day-15 按分隔符拆分字符串

    思路 依次对words的每个字符进行split(),然后将非空的加入List 解题方法 String arr[]=s.split(ss);利用split()方法将words的每个字符串划分为String数组 if(arr[i]!=“”) //将非空的加入 list.add(arr[i]); String ss=“”+separator; //使用转义字符 时间复杂度: O(mn) 空间复杂度: O(n) Code

    2024年01月20日
    浏览(38)
  • leetcode-2788按分隔符拆分字符串

    题目链接 2788. 按分隔符拆分字符串 - 力扣(LeetCode) 解题思路

    2024年01月21日
    浏览(40)
  • LeetCode.2788. 按分隔符拆分字符串

    题目链接 题目的意思是给我们一个字符串数组和一个分隔符,让我们按照分隔符把字符串数组分割成新的字符串数组。 看到这个描述,这不就是直接就是利用 按照分隔符分割字符串的系统库函数split() ,这个函数的意思就是 把一个字符串按照你给定的分隔符分割成字符串数

    2024年01月23日
    浏览(69)
  • Java8 List集合如何指定打印分隔符

    背景 无论是在学习还是日常的应用开发过程中,我们经常会需要使用分隔符将 List 集合打印出来。 如下所示: 执行结果如下: 如果我们想在打印的时候不打印 [] ,然后将分隔符换成 、 顿号,应该怎么操作呢? 其实有两种方法可以实现: 方法一:String.join(推荐) String

    2024年02月10日
    浏览(41)
  • 最优字符串分隔符:零宽度空格和字符

    在文本处理和格式化中,选择合适的分隔符是至关重要的。 本文将在介绍两个不常见但功能强大的分隔符:零宽度空格和𐀀字符。 零宽度空格是Unicode字符集中的一个特殊字符,其Unicode编码为U+200B。 零宽度空格在文本中不占据任何宽度,因此是一个不可见的分隔符。 主要应

    2024年02月21日
    浏览(40)
  • Hive中split函数分隔符为分号时报错问题

    今天写 SQL 的时候遇到一个问题,就是使用 split() 函数时,分隔符用 ; 就会报错,语句如下: 报错如下: Error while compiling statement: FAILED: ParseException line 1:17 cannot recognize input near \\\'EOF\\\' \\\'EOF\\\' \\\'EOF\\\' in select expression 一开始以为是转义字符的问题,但是无论是使用 \\\';\\\' ,还是 \\\'\\\\;\\\' ,或

    2024年02月09日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包