How to customize permission expressions in Spring Security

In the previous article , Song Ge has already talked with his friends about the permission expressions in Spring Security. Those who have not seen it can read it first. This article will continue to improve on the basis of the previous article:

1. SpEL Review

After the study of the previous article , friends already know that in Spring Security, annotations such as @PreAuthorize and @PostAuthorize all support SpEL expressions.

In the SpEL expression, if you directly write the name of the method to be executed, it means that this method is a method in the RootObject object. If you want to execute the method of other objects, you also need to write the name of the object, such as the following two example:

@PreAuthorize("hasAuthority('system:user:add')")
public String add() {
    return "add";
}
复制代码

In the above example, the method in the expression is hasAuthority, and no object name is written, so this method is a method in the RootObject object in SpEL.

@PreAuthorize("@ss.hasPermi('monitor:operlog:list')")
@GetMapping("/list")
public TableDataInfo list(SysOperLog operLog) {
    startPage();
    List<SysOperLog> list = operLogService.selectOperLogList(operLog);
    return getDataTable(list);
}
复制代码

In the above example, the expression method in the permission annotation is @ss.hasPermi('monitor:operlog:list'), where ss refers to an object name in the Spring container, and hasPermi is the method in this object.

Well, after the study of the previous article , everyone has mastered these basic knowledge.

2. How to customize

In fact, the second example given above is a custom example.

However, this customization is too liberal to be done within the Spring Security framework. So, today I want to talk to my friends about how to customize an expression for permission judgment without using third-party objects.

首先小伙伴们知道,我们在 @PreAuthorize 注解中使用的不用加对象名就能调用的权限方法,如 hasAuthorityhasPermissionhasRolehasAnyRole 等,基本上都是由 SecurityExpressionRoot 及其子类提供的,准确来说是由 MethodSecurityExpressionRoot 类提供的。

MethodSecurityExpressionRoot 类实际上继承自 SecurityExpressionRoot,只不过增加了过滤对象以及返回值对象。我们来看下 MethodSecurityExpressionRoot 的方法摘要:

再来看看 SecurityExpressionRoot 中的方法:

这些就是 RootObject 对象中的所有方法了,也是我们能够在 @PreAuthorize 注解中使用的所有方法了。

那么现在想在已有方法上继续扩展新方法,那么我们可以通过自定义类继承自 SecurityExpressionRoot 对象,扩展这个 RootObject 对象,在该对象中继续添加新的方法,进而实现自定义权限表达式。

好啦,说干就干,开搞!

本文的案例在前文的基础上继续完成,所以这里我就不从头开始写了。

3. 自定义 ExpressionRoot

首先我们自定义一个类继承自 SecurityExpressionRoot 并实现 MethodSecurityExpressionOperations 接口(本来直接继承自 MethodSecurityExpressionRoot 即可,但是因为这个类不是 public 的,没法继承,所以我们就实现 MethodSecurityExpressionOperations 接口即可):

public class CustomSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    private Object filterObject;
    private Object returnObject;
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * Creates a new instance
     *
     * @param authentication the {@link Authentication} to use. Cannot be null.
     */
    public CustomSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    /**
     * 判断当前对象是否具备某一个权限
     * @param permission
     * @return
     */
    public boolean hasPermission(String permission) {
        //获取当前登录用户所具有的权限
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if (antPathMatcher.match(authority.getAuthority(), permission)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否具备多个权限中的任意一个权限
     * @param permissions
     * @return
     */
    public boolean hasAnyPermissions(String... permissions) {
        if (permissions == null || permissions.length == 0) {
            return false;
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            for (String permission : permissions) {
                if (antPathMatcher.match(authority.getAuthority(), permission)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean hasAllPermissions:(String... permissions) {
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        if (permissions == null || permissions.length == 0) {
            return false;
        }
        for (String permission : permissions) {
            boolean flag = false;
            for (GrantedAuthority authority : authorities) {
                if (antPathMatcher.match(authority.getAuthority(), permission)) {
                    flag = true;
                }
            }
            if (!flag) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void setFilterObject(Object filterObject) {
        this.filterObject = filterObject;
    }

    @Override
    public Object getFilterObject() {
        return filterObject;
    }

    @Override
    public void setReturnObject(Object returnObject) {
        this.returnObject = returnObject;
    }

    @Override
    public Object getReturnObject() {
        return returnObject;
    }

    @Override
    public Object getThis() {
        return this;
    }
}
复制代码

加了 @Override 注解的方法,都是普普通通的常规方法,没啥好说的。我们自己主要实现了三个方法,分别是:

  • hasPermission:判断当前用户是否具备某一个给定的权限。
  • hasAnyPermissions:判断当前用户是否具备给定的多个权限中的某一个。
  • hasAllPermissions:判断当前用户是否具备所有的给定的权限。

这里边的逻辑我就不啰嗦了,都是基本的 Java 语法而已。

另外,用 AntPathMatcher 做比对是为了支持通配符,这个在上篇文章中已经说过了,这里不再赘述。

Spring Security 中,MethodSecurityExpressionRoot 的配置是通过 DefaultMethodSecurityExpressionHandler 来完成的,现在我们自定义了 CustomSecurityExpressionRoot,那也得有一个 Handler 来配置 CustomSecurityExpressionRoot,所以,再来一个类继承自 DefaultMethodSecurityExpressionHandler,如下:

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
        CustomSecurityExpressionRoot root = new CustomSecurityExpressionRoot(authentication);
        root.setTrustResolver(getTrustResolver());
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setRoleHierarchy(getRoleHierarchy());
        return root;
    }
}
复制代码

在 createSecurityExpressionRoot 方法中创建一个 CustomSecurityExpressionRoot 对象,对象的 TrustResolver、权限评估器以及角色层级等,统统都用默认的方案即可。

配置完成后,再配置一下 CustomMethodSecurityExpressionHandler 这个 Bean 即可,如下:

@Bean
CustomMethodSecurityExpressionHandler customMethodSecurityExpressionHandler() {
    return new CustomMethodSecurityExpressionHandler();
}
复制代码

好啦,这就注入成功了。

接下来,我们就可以在权限注解中使用这个自定义的方法了:

@PreAuthorize("hasPermission('system:user:add')")
public String add() {
    return "add";
}
复制代码

这个自定义权限表达式的思路,说到底还是在 Spring Security 体系中玩,个人感觉这种方式更合理一些。

In the TienChin project, Song Ge will also transform the RuoYi-Vue scaffolding according to this idea. At that time, in the video of the TienChin project, I will chat with you in detail. For those who are interested in the video, click here: The supporting video of the TienChin project is here .

Guess you like

Origin juejin.im/post/7119086250610409508