Shiro permission control principle and permission separator use

AuthorizingRealm

AuthorizingRealm is a key class for shiro to control login authentication and authorization. This class integrates AuthenticatingRealm and provides two abstract methods:
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

Usually these two abstract methods need to be implemented by ourselves, that is, to customize the Realm class, such as:

public class UserRealm extends AuthorizingRealm

There are many specific implementation methods on the Internet, so the code will not be posted here. Let's focus on doGetAuthorizationInfo. This method mainly does one thing, which is to add the current user's role and permission information to the AuthorizationInfo instance for subsequent authentication. The sample code is as follows:

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

Authentication principle

There are still the following three methods in the AuthorizingRealm class for authentication, and the last method belongs to the specific authentication implementation. The first one belongs to the custom PermissionResolver, which will not be introduced in this article. Mainly introduce the second default authentication processing method.

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

It can be seen from the method that the first step is to extract the user's role permission information from AuthorizationInfo according to the identity of the logged-in user. The specific implementation is as follows:

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

Pay attention to the last two sentences of the code:

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

The default processing class of resolver for the first sentence is:

WildcardPermissionResolver

Then the resolvePermission method is implemented as:

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

Continue to go inside the source code and find the following implementation method:

   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.");
        }
    }

Authorization CODE separator

Focus on the two split methods, the separators are colon and comma, which is why many permission codes defined by Shiro have colons or commas in the middle. For the time being, the code defined before the colon is the resource permission, and the code defined before the comma is the operation permission. Usually resources come first, followed by operations. Therefore, the source code is also separated by a colon first, and then separated by a comma, and finally the separated characters are placed in

List<Set<String>>

Reserve in the collection.

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

For example, we define a permission code as system_manager:user_manager:list, and the final collection is the List with a size of 3, and each of the 3 Set collections has an element. [system_manager, user_manager, list] these three strings.

For example, we define a permission code as system_manager, user_manager, list, and the final collection is that the size of the List is 1, and 1 Set contains 3 elements. [system_manager, user_manager, list] these three strings.

The permission judgment logic implemented by the two types of separators is also different, which can be tested in the DEMO at the end of the article.

permission check

Go back to the method protected boolean isPermitted(Permission permission, AuthorizationInfo info) in the previous screenshot. As mentioned earlier, the second parameter is used to obtain the permissions owned by the user and separate them with separators. The first parameter is to define sub-access permissions for this interface when we request an interface. For example, defined in the form of annotations:

@RequiresPermissions("system_manager:user_manager:list")

After understanding the method parameters, continue to look at the implementation method inside:

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

First the for loop iterates through all the permissions the user has. Then it is to see whether the permissions owned by the user match the permissions defined by the interface access, and the match proves that the user has permissions and can access the interface. A mismatch means no access.

Permission verification rules

The point is this method perm.implies(permission), the implementation source code is as follows:

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

Focus on the return result, the first return true needs to wait for the second return false result, so let’s talk about it first

The second returns false.

Looking at the if condition, you can know that only when the user permission does not exist * and there is an interface permission code that is not included in the user permission code, it will return false without permission .

It's a bit confusing here. First, it needs to be explained that entering this method is to traverse every permission code owned by the user, and compare it with the permission code defined by the interface. Then, no matter the permission code owned by the user or the permission code defined by the interface is split to form a Set<String>.

Assuming that the user permission code currently passed into the method is: system_manager:user_manager:create, and the interface permission code is system_manager:user_manager:list, the second return false will be triggered.

How will it trigger the return of the first return true?

Looking at the source code, we know that when the separated length of the user permission code is less than the code of the access permission defined by the interface, it will be triggered. What does that mean? That is, when the length of the user permission code is less than or equal to the length of the permission code defined by the interface, the second return false judgment will be made first. If the return false is not returned, the return true will be triggered. for example:

The user permission code is defined as: system_manager:user_manager, and the interface permission code is system_manager:user_manager:list, then the first return true will be triggered.

The third return false

If there is no * sign in the user permission code, no permission will be returned. For example, if the user permission code is defined as: system_manager:user_manager:list:check, and the interface permission code is system_manager:user_manager:list, the third return false will be triggered.

When none of the above three returns, return the fourth return true

Tool class for permission CODE judgment

Below is a tool class for comparing interface permission code and user permission code written by myself, which can conveniently allow users to judge some abnormal scenarios of permission verification without DEBUG in SHIRO source code.

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

Guess you like

Origin blog.csdn.net/h363659487/article/details/131412223