如何在 SpEL 的 @PostFilter 中正确使用 instanceof
支付阿杰
结论
原表达式不合法,原因有两个:
instanceof被拼成了instanseof。- 在 SpEL 中,类型通常要通过
T(...)引用,不能直接把Project、Phase等类名写在instanceof右侧。
正确写法如下:
filterObject instanceof T(com.example.domain.Project)
请将 com.example.domain.Project 替换为类的真实全限定名。
正确写法
可以继续使用嵌套三元表达式,但最好加上括号,让条件层级更清楚:
@PostFilter(
"filterObject instanceof T(com.example.domain.Project) " +
"? filterObject.manager == principal " +
": (filterObject instanceof T(com.example.domain.Phase) " +
"? filterObject.project.manager == principal " +
": (filterObject instanceof T(com.example.domain.Invoice) " +
"? filterObject.phase.project.manager == principal " +
": (filterObject instanceof T(com.example.domain.Payment) " +
"? filterObject.invoice.phase.project.manager == principal " +
": true)))"
)
public List<Entity> getData() {
// ...
}
T(...) 是 SpEL 的类型引用语法。运行时,@PostFilter 会依次将返回集合中的元素放入 filterObject,并根据表达式的计算结果决定是否保留。
三元表达式只计算命中的分支。因此,当 filterObject 是 Project 时,表达式不会继续访问 filterObject.project 等不存在的属性。
更易维护的方案
如果类型和权限规则还会增加,最好不要把所有逻辑都塞进注解,可以将判断移到独立的 Bean 中:
@Component
public class DataPermissionChecker {
public boolean canRead(Entity entity, Object principal) {
if (entity instanceof Project project) {
return Objects.equals(project.getManager(), principal);
}
if (entity instanceof Phase phase) {
return Objects.equals(phase.getProject().getManager(), principal);
}
if (entity instanceof Invoice invoice) {
return Objects.equals(
invoice.getPhase().getProject().getManager(),
principal
);
}
if (entity instanceof Payment payment) {
return Objects.equals(
payment.getInvoice()
.getPhase()
.getProject()
.getManager(),
principal
);
}
return true;
}
}
注解中只需保留一次方法调用:
@PostFilter("@dataPermissionChecker.canRead(filterObject, principal)")
public List<Entity> getData() {
// ...
}
这样更容易调试和测试,也可以避免 SpEL 表达式过长。
注意事项
principal的实际类型由认证配置决定,可能是UserDetails、自定义用户对象或字符串用户名,不一定能直接与manager比较。- 如果
manager是业务用户实体,而principal是UserDetails,应比较用户 ID、用户名等稳定标识,不要直接使用==。 - SpEL 中的
filterObject.manager这类属性写法通常会调用对应的getManager()。 - 原示例的
Invoice分支使用了filterObject.phase.project == principal,与其他分支比较manager的逻辑不一致,需要确认是否漏写了.manager。 - 如果未知的
Entity子类型不应放行,应将最后的true改为false。未知类型默认返回true可能造成权限绕过。 - 返回类型最好声明泛型,例如
List<Entity>,避免使用原始类型List。
备注:内容仅供参考。
