乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Security 基础

    • 本章定位
    • Spring Security 是什么
    • DelegatingFilterProxy
    • 安全过滤器链
    • SecurityFilterChain
    • 过滤器执行顺序
  • 第2章 认证

    • 本章定位
    • Authentication
    • 认证流程
    • AuthenticationManager
    • ProviderManager
    • DaoAuthenticationProvider
    • UserDetails
    • UserDetailsService
    • PasswordEncoder
    • BCryptPasswordEncoder
    • DelegatingPasswordEncoder
    • 表单登录
    • SecurityContext
    • SecurityContextHolder
    • UsernamePasswordAuthenticationToken
  • 第3章 授权

    • 本章定位
    • 授权模型
    • GrantedAuthority
    • AccessDecisionManager
    • AccessDecisionVoter
    • URL 级别授权
    • 方法级别安全
    • @PreAuthorize
    • @PostAuthorize
    • @PreFilter
    • @PostFilter
    • @Secured
    • RoleHierarchy
  • 第4章 过滤器链

    • 本章定位
    • FilterChainProxy
    • SecurityContextHolderFilter
    • LogoutFilter
    • BasicAuthenticationFilter
    • CsrfFilter
    • CorsFilter
    • HeaderWriterFilter
    • AnonymousAuthenticationFilter
    • RequestCacheAwareFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor
  • 第5章 会话管理

    • 本章定位
    • 会话管理
    • SessionFixation
    • 会话并发控制
    • SessionCreationPolicy
    • RememberMe
  • 第6章 JWT

    • 本章定位
    • JWT
    • JwtDecoder
    • JWT 认证
    • JwtAuthenticationConverter
  • 第7章 OAuth2

    • 本章定位
    • OAuth2 基础
    • OAuth2 Client
    • OAuth2 Resource Server
    • 第三方登录配置
  • 第8章 攻击防护

    • 本章定位
    • CSRF 跨站请求伪造防护
    • CORS 跨域防护
    • Clickjacking 点击劫持防护
    • 安全响应头
    • Session Fixation 会话固定防护
  • 第9章 测试

    • 本章定位
    • 安全测试
    • @WithMockUser
    • 最佳实践

PreFilter

一句话定义:方法执行前过滤参数集合,使用 SpEL 表达式遍历传入的集合参数,移除不符合权限条件的元素,只保留合法元素传递给目标方法。


定义与作用

@PreFilter 在方法执行前拦截,对传入的集合类型参数进行过滤。表达式中的 filterObject 代表集合中的单个元素,表达式返回 false 的元素会被从集合中移除,不会进入目标方法。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreFilter {
    String value();  // SpEL 表达式,filterObject 代表集合元素
}

关键变量:

变量类型含义
filterObject集合元素类型当前遍历到的集合元素
filterTarget隐式Spring Security 自动检测的集合参数
#paramName方法参数其他参数引用
authenticationAuthentication当前安全上下文

支持的集合类型:

  • Collection 接口实现(List, Set, ArrayList, HashSet 等)
  • 数组(Object[])
  • Map(filterObject 为 Map.Entry)

核心原理

过滤执行流程

核心机制:@PreFilter 通过 MethodSecurityExpressionHandler 执行过滤,实际上是修改(或替换)传入的集合参数。对于不可变集合(如 Collections.emptyList()),会抛出异常或创建新集合。


示例一:批量操作前过滤非所属数据

场景说明

用户提交一批任务 ID 进行批量更新。系统只允许用户更新自己创建的任务,需要过滤掉不属于当前用户的任务。

操作前配置(不安全的批量处理)

@Service
public class TaskService {

    @PreAuthorize("hasRole('USER')")
    public void batchUpdate(List<Task> tasks) {
        for (Task task : tasks) {
            taskRepository.save(task);  // 可以更新任何人的任务!
        }
    }
}

// 调用方
taskService.batchUpdate(Arrays.asList(
    myTask,      // 自己的任务
    othersTask   // 别人的任务 ❌ 也能更新!
));

问题:@PreAuthorize 只检查角色,无法逐个检查集合元素的所有权,导致越权修改。

操作后配置(使用 @PreFilter)

@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");
    }
}

public class Task {
    private Long id;
    private String title;
    private String owner;  // 任务所有者
    // getters/setters
}

@Service
public class TaskService {

    @Autowired
    private TaskRepository taskRepository;

    // 过滤掉不属于当前用户的任务,只保留自己创建的
    @PreAuthorize("hasRole('USER')")
    @PreFilter("filterObject.owner == authentication.name")
    public void batchUpdate(List<Task> tasks) {
        // 此时 tasks 已被过滤,只包含当前用户的任务
        for (Task task : tasks) {
            taskRepository.save(task);
        }
        System.out.println("实际更新了 " + tasks.size() + " 条任务");
    }
}

结果分析

alice 调用 batchUpdate(Arrays.asList(aliceTask1, aliceTask2, bobTask1)):

元素owner表达式评估结果
aliceTask1"alice""alice" == "alice"✅ 保留
aliceTask2"alice""alice" == "alice"✅ 保留
bobTask1"bob""bob" == "alice"❌ 移除
  • 进入 batchUpdate 方法的 tasks 实际只有 [aliceTask1, aliceTask2]
  • bobTask1 被静默过滤,不会报错也不会被更新

示例二:多集合参数时指定 filterTarget

场景说明

方法有多个集合参数,需要显式指定哪个参数应该被过滤。

操作前配置(错误:多集合参数未指定目标)

@Service
public class ProjectService {

    // 错误:有多个集合参数,Spring 不知道过滤哪个
    @PreFilter("filterObject.owner == authentication.name")
    public void mergeTasks(List<Task> sourceTasks, List<Task> targetTasks) {
        // 可能报错或行为不确定
    }
}

操作后配置(使用 filterTarget 指定)

@Service
public class ProjectService {

    // 明确指定过滤 sourceTasks 参数
    @PreFilter(value = "filterObject.owner == authentication.name", 
               filterTarget = "sourceTasks")
    public void mergeTasks(List<Task> sourceTasks, List<Task> targetTasks) {
        // sourceTasks 已被过滤,只包含当前用户的任务
        // targetTasks 完整保留,不做过滤
        for (Task task : sourceTasks) {
            targetTasks.add(task.clone());
        }
    }
}

结果分析

alice 调用 mergeTasks(sourceList, targetList):

  • sourceList 包含 [aliceTask, bobTask] → 过滤后只剩 [aliceTask]
  • targetList 包含 [existingTask] → 完整保留,不被过滤
  • 方法执行后 targetList 增加 aliceTask 的副本

易错场景:不可变集合导致 UnsupportedOperationException

场景:调用方传入不可变集合(如 Arrays.asList() 在某些 JDK 版本中返回固定大小列表,或 Collections.unmodifiableList()),@PreFilter 尝试移除元素时抛出异常。

// 调用方
List<Task> tasks = Arrays.asList(task1, task2, task3);  // 固定大小列表
taskService.batchUpdate(tasks);  // 可能抛出异常
@Service
public class TaskService {

    @PreFilter("filterObject.owner == authentication.name")
    public void batchUpdate(List<Task> tasks) {
        // 如果 tasks 是不可变集合,Spring Security 尝试 remove() 时抛出异常
    }
}

问题根源:@PreFilter 默认尝试在原集合上调用 remove() 方法。如果集合不可变(如 Arrays.asList() 返回的列表或 Collections.unmodifiableList()),会抛出 UnsupportedOperationException。

正确做法:

@Service
public class TaskService {

    // 方案一:方法内部复制为可变集合(绕过 @PreFilter,改用 @PreAuthorize + 手动过滤)
    @PreAuthorize("hasRole('USER')")
    public void safeBatchUpdate(List<Task> tasks) {
        // 手动复制和过滤
        List<Task> filtered = tasks.stream()
            .filter(t -> t.getOwner().equals(
                SecurityContextHolder.getContext().getAuthentication().getName()
            ))
            .collect(Collectors.toList());
        
        for (Task task : filtered) {
            taskRepository.save(task);
        }
    }

    // 方案二:调用方传入可变集合
    // List<Task> tasks = new ArrayList<>(Arrays.asList(task1, task2, task3));
    // taskService.batchUpdate(tasks);
}

面试考点:

问:@PreFilter 和 @PostFilter 对集合有什么要求?传入不可变集合会怎样? 答:@PreFilter 和 @PostFilter 底层都通过迭代器 remove() 操作集合。如果传入的是不可变集合(如 Arrays.asList() 在某些 JDK 版本返回固定大小列表、Collections.unmodifiableList()、Java 9+ 的 List.of()),调用 remove() 会抛出 UnsupportedOperationException。解决方案:① 调用方传入 ArrayList 等可变集合;② 方法内部不使用 @PreFilter,改为手动 Stream 过滤;③ 使用 @PreAuthorize 配合方法内部逻辑处理。生产环境中,如果集合来源不确定,应避免依赖 @PreFilter 的自动过滤,改用显式代码过滤更安全可控。


@PreFilter 速查表

特性说明
执行时机方法调用前,参数传入后
核心变量filterObject(集合元素)
多集合参数使用 filterTarget = "paramName" 指定
返回值过滤后的集合(原地修改或替换)
不可变集合会抛出 UnsupportedOperationException
空集合不过滤,直接传入空集合
数组支持支持,但过滤后数组大小不变(null 填充)
与 @PreAuthorize 配合通常同时使用,先角色检查再元素过滤
上一页
@PostAuthorize
下一页
@PostFilter