PostFilter
一句话定义:方法执行后过滤返回值集合,使用 SpEL 表达式遍历方法返回的集合,只保留符合条件(表达式返回 true)的元素,将过滤后的集合返回给调用方。
定义与作用
@PostFilter 在方法执行完毕后,对返回的集合类型结果进行过滤。与 @PreFilter 不同,它作用于返回值而非参数。表达式中的 filterObject 代表返回集合中的单个元素,表达式返回 false 的元素会被移除,不会返回给调用方。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PostFilter {
String value(); // SpEL 表达式,filterObject 代表返回集合元素
}
关键变量:
| 变量 | 类型 | 含义 |
|---|---|---|
filterObject | 集合元素类型 | 当前遍历到的返回集合元素 |
returnObject | 完整返回值 | 整个返回集合(也可通过 returnObject 访问) |
authentication | Authentication | 当前安全上下文 |
principal | Object | 当前用户主体 |
核心原理
过滤执行流程
关键特性:
@PostFilter在方法执行后过滤,因此方法已经查询了全部数据,只是在返回给调用方前做了一次内存过滤。这意味着方法内部已经获取了所有数据,只是调用方看不到未授权的部分。
示例一:查询结果按用户过滤
场景说明
任务管理系统,调用 getAllTasks() 返回所有任务,但普通用户只能看到自己创建的任务,Admin 可以看到全部。
操作前配置(不安全的查询)
@Service
public class TaskService {
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
public List<Task> getAllTasks() {
return taskRepository.findAll(); // 返回所有人的任务!
}
}
问题:任何已登录用户都能看到所有任务,存在数据泄露。
操作后配置(使用 @PostFilter)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TaskSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("alice").password("{noop}alice").roles("USER")
.and()
.withUser("bob").password("{noop}bob").roles("USER")
.and()
.withUser("admin").password("{noop}admin").roles("ADMIN");
}
}
public class Task {
private Long id;
private String title;
private String owner; // 任务所有者
// getters/setters
}
@Service
public class TaskService {
@Autowired
private TaskRepository taskRepository;
// 返回所有任务,但只保留当前用户的任务(或 Admin 保留全部)
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@PostFilter("hasRole('ADMIN') or filterObject.owner == authentication.name")
public List<Task> getAllTasks() {
return taskRepository.findAll(); // 查询全部,但返回前过滤
}
// 简化版:普通用户只看到自己的任务,Admin 看到全部
@PreAuthorize("hasRole('USER')")
@PostFilter("filterObject.owner == authentication.name")
public List<Task> getMyTasks() {
return taskRepository.findAll();
}
}
结果分析
alice 调用 getAllTasks():
| 数据库返回 | owner | 表达式评估 | 返回给 alice |
|---|---|---|---|
| Task1 | "alice" | "alice" == "alice" | ✅ 保留 |
| Task2 | "bob" | "bob" == "alice" | ❌ 移除 |
| Task3 | "alice" | "alice" == "alice" | ✅ 保留 |
| Task4 | "bob" | "bob" == "alice" | ❌ 移除 |
- alice 最终看到
[Task1, Task3] - admin 调用
getAllTasks():hasRole('ADMIN')为true,短路求值,所有任务保留
示例二:@PostFilter 与 @PostAuthorize 的区别
场景说明
对比两种"返回后校验"方式的差异。@PostAuthorize 是二元的(通过或拒绝整个方法),@PostFilter 是过滤的(允许部分元素通过)。
操作前配置(使用 @PostAuthorize,结果过于严格)
@Service
public class DocumentService {
// 使用 @PostAuthorize:要求返回的文档必须属于当前用户
@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(Long id) {
return documentRepository.findById(id).orElse(null);
}
// 错误:@PostAuthorize 不适用于集合,因为 returnObject 是 List
@PostAuthorize("???") // 无法表达"只返回属于我的文档"
public List<Document> getAllDocuments() {
return documentRepository.findAll();
}
}
问题:@PostAuthorize 只能判断单一返回值,无法处理集合过滤。当查询全部文档时,无法只返回部分文档。
操作后配置(@PostAuthorize 用于单条,@PostFilter 用于集合)
@Service
public class DocumentService {
@Autowired
private DocumentRepository documentRepository;
// 单条查询:用 @PostAuthorize 判断所有权
@PostAuthorize("returnObject == null or returnObject.owner == authentication.name")
public Document getDocument(Long id) {
return documentRepository.findById(id).orElse(null);
}
// 集合查询:用 @PostFilter 过滤返回结果
@PreAuthorize("hasRole('USER')")
@PostFilter("filterObject.owner == authentication.name")
public List<Document> getMyDocuments() {
return documentRepository.findAll(); // 查询全部,返回前过滤
}
// Admin 查询:不过滤,返回全部
@PreAuthorize("hasRole('ADMIN')")
public List<Document> getAllDocumentsAdmin() {
return documentRepository.findAll();
}
// 组合场景:混合返回
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@PostFilter("hasRole('ADMIN') or filterObject.owner == authentication.name or filterObject.isPublic")
public List<Document> getVisibleDocuments() {
return documentRepository.findAll();
}
}
结果分析
| 方法 | 注解 | 用户 | 行为 |
|---|---|---|---|
getDocument(1) | @PostAuthorize | alice | 如果文档 1 属于 alice 则返回,否则 403 |
getMyDocuments() | @PostFilter | alice | 返回 alice 的所有文档,不报错 |
getMyDocuments() | @PostFilter | bob | 返回 bob 的所有文档 |
getVisibleDocuments() | @PostFilter | 匿名 | 无法调用(@PreAuthorize 拦截) |
getVisibleDocuments() | @PostFilter | alice | 返回自己的 + 公开的文档 |
getVisibleDocuments() | @PostFilter | admin | 返回全部(短路) |
易错场景:@PostFilter 的内存消耗与性能陷阱
场景:@PostFilter 配合 findAll() 查询,在数据量巨大时导致内存溢出或性能问题。
@Service
public class OrderService {
@PreAuthorize("hasRole('USER')")
@PostFilter("filterObject.owner == authentication.name")
public List<Order> getAllOrders() {
return orderRepository.findAll(); // 查询 100万 条订单!
}
}
问题:
orderRepository.findAll()加载了全表数据到内存@PostFilter只保留属于当前用户的几十条- 浪费了 99.99% 的查询结果和内存
- 高并发时数据库和 JVM 压力巨大
正确做法:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
// 方案一:查询时直接过滤(最优,数据库层面过滤)
@PreAuthorize("hasRole('USER')")
public List<Order> getMyOrders() {
String currentUser = SecurityContextHolder.getContext()
.getAuthentication().getName();
return orderRepository.findByOwner(currentUser); // 数据库只返回相关数据
}
// 方案二:如果必须用 @PostFilter,配合分页避免全表加载
@PreAuthorize("hasRole('USER')")
@PostFilter("filterObject.owner == authentication.name")
public List<Order> getRecentOrders(Pageable pageable) {
return orderRepository.findRecentOrders(pageable); // 只查最近 N 条
}
// 方案三:@PostFilter 仅用于小数据量、多维度权限的复杂场景
@PreAuthorize("hasRole('ADMIN')")
@PostFilter("filterObject.deptCode == authentication.principal.deptCode")
public List<Report> getDeptReports() {
return reportRepository.findAll(); // 仅用于管理后台,数据量可控
}
}
面试考点:
问:
@PostFilter和@PreAuthorize+ 数据库查询过滤有什么区别?@PostFilter有什么性能问题? 答:@PostFilter是在方法执行后对内存中的集合进行过滤,方法内部已经获取了全部数据,只是在返回前做了一次内存裁剪。这种方式的问题在于:① 方法内部已经查询了全部数据,数据库 I/O 和内存占用没有节省;② 对于大数据量场景(如findAll()),会加载海量数据到内存后过滤掉大部分,造成严重的性能浪费和 OOM 风险。最佳实践是:对于数据量大的场景,应在数据库查询层直接过滤(如WHERE owner = ?),配合@PreAuthorize做角色检查;@PostFilter只适用于数据量可控、权限规则复杂难以在 SQL 中表达的场景(如多维度组合权限)。
@PostFilter vs @PostAuthorize 速查
| 特性 | @PostAuthorize | @PostFilter |
|---|---|---|
| 执行时机 | 方法执行后 | 方法执行后 |
| 适用返回类型 | 任意类型(单对象) | 集合/数组 |
| 核心变量 | returnObject | filterObject, returnObject |
| 失败行为 | 抛出 AccessDeniedException | 移除元素,不抛异常 |
| 内存效率 | 正常 | 方法已获取全部数据,可能浪费 |
| 典型场景 | 单条查询后校验所有权 | 集合查询后过滤可见数据 |