Spring Security基于方法级别的自定义表达式(可以完成任何权限判断)
背景
需求是这样的:项目采用的前后端分离的架构,且使用的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);
}
如果用户访问A,但是没有CAN_READ_A
权限,接口将会返回403。
转载自:https://juejin.cn/post/6844903905889910797