需求是这样的:项目采用的前后端分离的架构,且使用的 RESTFUL 风格API,同一个资源的相关请求是一样的url,但是http method不一样。
如果要允许一个人获取某个资源,但是不能创建它,显然基于url的权限设计显然是无法满足需求的。
当我查阅到了可以基于方法的权限控制之后,我认为这应该是个最佳方案。但是却存在这样一个问题,一个方法针对不同的入参可能会触发不同的权限。比如说,一个用户拥有查看A目录的权限,但是没有查看B目录的权限。而这两个动作都是调用的同一个Controller方法,只是根据入参来区分查看不同的目录。
默认的 hasAuthority
和 hasRole
表达式都无法满足需求,因为它们只能判断一个硬编码的权限或者角色字符串。所以我们需要用到自定义表达式来高度自定义权限判断以满足需求。下面我们就来具体介绍如何使用它。
我们将创建一个 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); } 复制代码