Spring Security基于方法级别的自定义表达式(可以完成任何权限判断)

·  阅读 2785

背景

需求是这样的:项目采用的前后端分离的架构,且使用的RESTFUL风格API,同一个资源的相关请求是一样的url,但是http method不一样。

如果要允许一个人获取某个资源,但是不能创建它,显然基于url的权限设计显然是无法满足需求的。

当我查阅到了可以基于方法的权限控制之后,我认为这应该是个最佳方案。但是却存在这样一个问题,一个方法针对不同的入参可能会触发不同的权限。比如说,一个用户拥有查看A目录的权限,但是没有查看B目录的权限。而这两个动作都是调用的同一个Controller方法,只是根据入参来区分查看不同的目录。

默认的hasAuthorityhasRole表达式都无法满足需求,因为它们只能判断一个硬编码的权限或者角色字符串。所以我们需要用到自定义表达式来高度自定义权限判断以满足需求。下面我们就来具体介绍如何使用它。

示例

我们将创建一个canRead的表达式。当入参为"A"时,将判断当前用户是否有查看A的权限;当入参为"B"时,将判断当前用户是否有查看B的权限。

配置

为了创建自定义表达式,我们首先需要实现root表达式:

public class CustomMethodSecurityExpressionRoot
        extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    private Object filterObject;
    private Object returnObject;

    public CustomMethodSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    // 我们的自定义表达式
    public boolean canRead(String foo) {
        if (foo.equals("A") && !this.hasAuthority("CAN_READ_A")) {
            return false;
        }

        if (foo.equals("B") && !this.hasAuthority("CAN_READ_B")) {
            return false;
        }
        
        return true;
    }

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

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

    @Override
    public Object getThis() {
        return this;
    }

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

    @Override
    public void setReturnObject(Object obj) {
        this.returnObject = obj;
    }
}
复制代码

接下来,我们需要把CustomMethodSecurityExpressionRoot注入到表达式处理器内:

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

然后需要把CustomMethodSecurityExpressionHandler写到方法安全配置里面:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        CustomMethodSecurityExpressionHandler expressionHandler = 
          new CustomMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator());
        return expressionHandler;
    }
}
复制代码

使用

@PreAuthorize("canRead(#foo)")
@GetMapping("/")
public Foo getFoo(@RequestParam("foo") String foo) {
    return fooService.findAll(foo);
}
复制代码

如果用户访问A,但是没有CAN_READ_A权限,接口将会返回403。

分类:
后端
标签:
分类:
后端
标签: