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

    • 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
    • 最佳实践

FilterSecurityInterceptor

定义与作用

FilterSecurityInterceptor 是 Spring Security 5.x 中负责 URL 级别授权决策 的核心过滤器。它位于整个安全过滤器链的末尾,是请求到达 Controller 之前的最后一道安全关卡。在 Spring Security 6.x 中,它的职责被 AuthorizationFilter 替代,但 5.x 的绝大多数生产系统仍依赖它执行最终的访问控制。

它的核心职责可概括为:

  1. 拦截请求:作为过滤器链的最后一个安全过滤器,拦截即将进入 Servlet 目标资源的请求。
  2. 获取权限配置:通过 SecurityMetadataSource 获取当前请求资源所需的 ConfigAttribute(如 ROLE_ADMIN)。
  3. 获取当前身份:从 SecurityContextHolder 中取出当前 Authentication(包含用户身份和已有权限)。
  4. 委托决策:将上述信息交给 AccessDecisionManager,由其内部的 AccessDecisionVoter 投票决定是否放行。
  5. 执行决策:如果 AccessDecisionManager 决定通过,则放行请求到目标资源;如果拒绝,则抛出 AccessDeniedException,由上游的 ExceptionTranslationFilter 捕获并转译为 403 Forbidden。

位置说明:在 5.x 的过滤器链中,FilterSecurityInterceptor 位于 ExceptionTranslationFilter 之后。ExceptionTranslationFilter 在 FilterSecurityInterceptor 的上游,专门捕获它抛出的 AuthenticationException 和 AccessDeniedException。


核心原理

1. 工作流程

FilterSecurityInterceptor 的完整授权决策流程如下:

步骤拆解:

步骤执行组件说明
①FilterSecurityInterceptor请求到达,准备执行授权检查
②SecurityMetadataSource根据请求 URL 查找对应的 ConfigAttribute 列表(如 ROLE_ADMIN)
③SecurityContextHolder获取当前线程中的 Authentication(可能来自 Session、JWT、Basic 等)
④AccessDecisionManager调用 decide(authentication, filterInvocation, configAttributes),内部遍历 AccessDecisionVoter 进行投票
⑤目标资源决策通过,请求继续向下到达 Controller / Servlet
⑥ExceptionTranslationFilter如果决策拒绝,AccessDecisionManager 或 FilterSecurityInterceptor 抛出 AccessDeniedException,由上游的 ExceptionTranslationFilter 捕获

2. 与 AccessDecisionManager 的协作

FilterSecurityInterceptor 本身并不直接判断"用户是否有权限",而是将判断委托给 AccessDecisionManager。FilterSecurityInterceptor 通过 AbstractSecurityInterceptor 的模板方法实现这一协作:

// 简化逻辑
protected InterceptorStatusToken beforeInvocation(Object object) {
    // 1. 获取资源所需权限
    Collection<ConfigAttribute> attributes = 
        this.obtainSecurityMetadataSource().getAttributes(object);
    
    // 2. 获取当前认证信息
    Authentication authentication = 
        SecurityContextHolder.getContext().getAuthentication();
    
    // 3. 委托 AccessDecisionManager 决策
    this.accessDecisionManager.decide(authentication, object, attributes);
    
    // 4. 返回 token(用于 afterInvocation,URL 级别通常不处理)
    return new InterceptorStatusToken(authentication, true, attributes, object);
}

AccessDecisionManager 的三种实现策略:

实现类决策规则适用场景
AffirmativeBased一票通过(只要有任意一个 Voter 投赞成票)默认策略,最常用
ConsensusBased多数通过(赞成票 > 反对票)需要多维度权衡
UnanimousBased一致通过(所有 Voter 必须赞成)严格安全场景

3. 与 ExceptionTranslationFilter 的交互

FilterSecurityInterceptor 与 ExceptionTranslationFilter 的交互时序如下:

关键点:

  • ExceptionTranslationFilter 在 FilterSecurityInterceptor 的上游,通过 try-catch 包裹 chain.doFilter() 捕获所有下游异常。
  • 如果当前用户是匿名用户(AnonymousAuthenticationToken),ExceptionTranslationFilter 会将 AccessDeniedException 视为未认证处理,调用 AuthenticationEntryPoint(返回 401 或重定向登录页),而不是直接返回 403。
  • 如果当前用户已认证(非匿名),则直接调用 AccessDeniedHandler 返回 403。

完整示例一:从默认授权到自定义 AccessDecisionManager

场景说明

某电商后台管理系统需要记录所有授权拒绝的日志(用于审计和风控)。默认的 AffirmativeBased 在拒绝时直接抛出 AccessDeniedException,没有任何日志痕迹。我们需要在不改变最终返回行为(仍返回 403)的前提下,在拒绝前打印日志。

操作前配置(默认行为)

@Configuration
@EnableWebSecurity
public class AdminSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/orders/**").hasAnyRole("ADMIN", "OPERATOR")
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin").password("{noop}admin").roles("ADMIN")
            .and()
            .withUser("operator").password("{noop}operator").roles("OPERATOR")
            .and()
            .withUser("user").password("{noop}user").roles("USER");
    }
}

问题分析:默认配置下,Spring Security 会自动创建 AffirmativeBased 作为 AccessDecisionManager。当 FilterSecurityInterceptor 调用 decide() 时,如果 RoleVoter 投反对票,直接抛出 AccessDeniedException。虽然请求最终返回 403,但控制台没有任何记录,无法满足审计需求。

操作后配置(自定义 AccessDecisionManager)

自定义 AccessDecisionManager,在委托给默认实现之前/之后插入日志逻辑:

@Component
public class LoggingAccessDecisionManager implements AccessDecisionManager {

    private final AccessDecisionManager delegate;

    public LoggingAccessDecisionManager() {
        List<AccessDecisionVoter<?>> voters = Arrays.asList(
            new RoleVoter(),
            new AuthenticatedVoter()
        );
        this.delegate = new AffirmativeBased(voters);
    }

    @Override
    public void decide(Authentication authentication, Object object,
                       Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException {
        try {
            // 委托给默认实现做决策
            delegate.decide(authentication, object, configAttributes);
        } catch (AccessDeniedException e) {
            // 记录授权拒绝日志
            String required = configAttributes.stream()
                .map(ConfigAttribute::getAttribute)
                .collect(Collectors.joining(", "));
            System.out.println("[授权拒绝] 用户=" + authentication.getName()
                + ", 请求=" + object
                + ", 所需权限=[" + required + "]"
                + ", 当前权限=" + authentication.getAuthorities());
            throw e;
        }
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return delegate.supports(attribute);
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

将自定义的 AccessDecisionManager 接入 HttpSecurity:

@Configuration
@EnableWebSecurity
public class AdminSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private LoggingAccessDecisionManager loggingAccessDecisionManager;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .accessDecisionManager(loggingAccessDecisionManager)  // 接入自定义决策器
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/orders/**").hasAnyRole("ADMIN", "OPERATOR")
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin").password("{noop}admin").roles("ADMIN")
            .and()
            .withUser("operator").password("{noop}operator").roles("OPERATOR")
            .and()
            .withUser("user").password("{noop}user").roles("USER");
    }
}

结果分析

启动应用后,分别用不同用户访问 /admin/dashboard:

用户请求控制台输出响应
user (ROLE_USER)GET /admin/dashboard[授权拒绝] 用户=user, 请求=FilterInvocation: .../admin/dashboard, 所需权限=[ROLE_ADMIN], 当前权限=[ROLE_USER]403 Forbidden
operator (ROLE_OPERATOR)GET /admin/dashboard[授权拒绝] 用户=operator, 请求=.../admin/dashboard, 所需权限=[ROLE_ADMIN], 当前权限=[ROLE_OPERATOR]403 Forbidden
admin (ROLE_ADMIN)GET /admin/dashboard无日志(投票通过)200 OK

关键点:FilterSecurityInterceptor 的调用链保持不变,只是在 AccessDecisionManager 层插入了日志切面。最终行为仍然是 AccessDeniedException → ExceptionTranslationFilter → 403,Spring Security 的异常处理链路没有被破坏。


完整示例二:动态权限加载(FilterInvocationSecurityMetadataSource)与 FilterSecurityInterceptor 配合

场景说明

某 SaaS 平台的管理后台权限需要从数据库动态加载,运营人员可以在后台调整某个 URL 所需的权限,无需重启应用。默认配置下,URL 授权规则在 HttpSecurity 中硬编码,任何修改都需要重新打包部署。

操作前配置(URL 规则硬编码)

@Configuration
@EnableWebSecurity
public class SaaSBackendConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")      // 硬编码
                .antMatchers("/finance/**").hasRole("FINANCE")  // 硬编码
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin").password("{noop}admin").roles("ADMIN", "FINANCE")
            .and()
            .withUser("user").password("{noop}user").roles("USER");
    }
}

问题分析:antMatchers("/admin/**").hasRole("ADMIN") 将规则写死在 Java 代码中。如果业务需要临时将 /finance/report 开放给 ADMIN 角色查看,必须修改代码、重新编译、重新部署。

操作后配置(自定义 FilterInvocationSecurityMetadataSource)

实现 FilterInvocationSecurityMetadataSource,从数据库(此处用内存模拟)读取 URL 与角色的映射:

@Service
public class DbPermissionService {

    // 模拟数据库表:url → 所需角色列表
    private static final Map<String, List<String>> PERMISSION_DB = new ConcurrentHashMap<>();

    public DbPermissionService() {
        // 初始化数据
        PERMISSION_DB.put("/admin/dashboard", Arrays.asList("ADMIN"));
        PERMISSION_DB.put("/admin/users", Arrays.asList("ADMIN"));
        PERMISSION_DB.put("/finance/report", Arrays.asList("FINANCE"));
    }

    public List<String> findRolesByUrl(String url) {
        // 精确匹配演示(生产环境可扩展为 Ant 模式匹配)
        return PERMISSION_DB.getOrDefault(url, Collections.emptyList());
    }

    public void updateRoles(String url, List<String> roles) {
        PERMISSION_DB.put(url, roles);
    }
}
@Component
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private DbPermissionService permissionService;

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) 
            throws IllegalArgumentException {
        FilterInvocation fi = (FilterInvocation) object;
        String url = fi.getRequestUrl();

        // 从数据库查询该 URL 所需的角色
        List<String> roles = permissionService.findRolesByUrl(url);

        if (roles.isEmpty()) {
            // 无特殊配置,返回 ROLE_ANONYMOUS(表示走默认规则)
            return SecurityConfig.createList("ROLE_ANONYMOUS");
        }

        String[] roleArray = roles.stream()
            .map(r -> "ROLE_" + r)
            .toArray(String[]::new);
        return SecurityConfig.createList(roleArray);
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

将自定义 SecurityMetadataSource 注入 FilterSecurityInterceptor:

@Configuration
@EnableWebSecurity
public class SaaSBackendConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DynamicSecurityMetadataSource securityMetadataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        // 替换默认的 SecurityMetadataSource
                        object.setSecurityMetadataSource(securityMetadataSource);
                        return object;
                    }
                })
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin").password("{noop}admin").roles("ADMIN", "FINANCE")
            .and()
            .withUser("user").password("{noop}user").roles("USER");
    }
}

结果分析

操作请求数据库权限结果
初始状态admin 访问 /finance/report["FINANCE"]200 OK(admin 拥有 FINANCE)
初始状态user 访问 /finance/report["FINANCE"]403 Forbidden(user 无 FINANCE)
动态修改后admin 访问 /finance/report调用 updateRoles("/finance/report", ["ADMIN", "FINANCE"])200 OK
动态修改后user 访问 /finance/report["ADMIN", "FINANCE"]403 Forbidden(user 无对应角色)

动态修改演示:

@RestController
public class PermissionAdminController {

    @Autowired
    private DbPermissionService permissionService;

    @PostMapping("/admin/permission/update")
    public String updatePermission(@RequestParam String url,
                                   @RequestParam List<String> roles) {
        permissionService.updateRoles(url, roles);
        return "权限已更新,下次访问生效";
    }
}

关键点:FilterSecurityInterceptor 每次请求都会调用 SecurityMetadataSource.getAttributes()。因此只要数据源返回的内容发生变化,无需重启应用,下一次请求就会自动获取新的权限配置。这与 HttpSecurity 硬编码方案形成了鲜明对比。


易错场景与面试考点

易错一:AccessDeniedException 被自定义全局异常处理器捕获

问题现象:开发者在项目中定义了 @ControllerAdvice 或 HandlerExceptionResolver,并捕获了 AccessDeniedException:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<String> handleAccessDenied(AccessDeniedException e) {
        return ResponseEntity.status(404).body("资源不存在");
    }
}

后果:FilterSecurityInterceptor 在过滤器链中抛出的 AccessDeniedException,正常情况下应由上游的 ExceptionTranslationFilter 捕获,调用 AccessDeniedHandler 返回 403。但如果该异常由于某种原因(例如通过 Spring Boot 的 Error Dispatch 机制或自定义 FilterRegistrationBean 的 DispatcherType.ERROR 配置)进入了 DispatcherServlet,就可能被 GlobalExceptionHandler 捕获,返回 404 或 500,而不是预期的 403。

正确做法:

  1. 不要在全局异常处理器中捕获 AccessDeniedException(除非你明确知道自己在做什么)。
  2. 如果需要自定义 403 响应,应通过 ExceptionTranslationFilter 配置 AccessDeniedHandler:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .exceptionHandling()
            .accessDeniedHandler((request, response, accessDeniedException) -> {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().write("{\"code\":403,\"message\":\"权限不足\"}");
            })
        .and()
        .authorizeRequests()
            .anyRequest().authenticated();
}
  1. 如果确实需要全局异常处理器处理某些特定的 AccessDeniedException(如方法级别 @PreAuthorize 抛出的异常),应在 GlobalExceptionHandler 中透传 FilterSecurityInterceptor 产生的 URL 级别异常,或明确区分异常来源。

易错二:自定义 FilterSecurityInterceptor 时位置放错

问题现象:开发者使用 http.addFilterAt() 或 http.addFilterBefore() 添加自定义的 FilterSecurityInterceptor,但将其放置在过滤器链中间位置,而不是末尾:

// 错误配置
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .addFilterAfter(new CustomFilterSecurityInterceptor(), 
                        UsernamePasswordAuthenticationFilter.class)  // 位置错误!
        .authorizeRequests()
            .anyRequest().authenticated();
}

后果:FilterSecurityInterceptor 之后还有其他过滤器(如 SwitchUserFilter、自定义的审计过滤器、日志过滤器等)。如果这些过滤器中的操作涉及敏感资源,它们将在授权检查之前执行,导致攻击者可以绕过授权。

正确做法:

在 5.x 中,FilterSecurityInterceptor 应该是过滤器链中最后一个执行安全操作的过滤器。如果需要自定义,应使用 withObjectPostProcessor 修改内置实例,或确保替换后的位置仍在末尾:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                    // 安全地修改内置实例的属性
                    object.setSecurityMetadataSource(customMetadataSource);
                    return object;
                }
            })
            .anyRequest().authenticated();
}

面试考点

Q1:FilterSecurityInterceptor 和 AuthorizationFilter(6.x)有什么区别?

维度FilterSecurityInterceptor (5.x)AuthorizationFilter (6.x)
决策机制基于 AccessDecisionManager + AccessDecisionVoter 投票基于 AuthorizationManager + AuthorizationDecision
接口风格继承 AbstractSecurityInterceptor,实现 Filter独立的 Filter 实现,职责更单一
异常处理抛出 AccessDeniedException 由 ExceptionTranslationFilter 捕获同样抛出异常,但 AuthorizationManager 的 check() 方法返回 AuthorizationDecision
元数据来源SecurityMetadataSource(FilterInvocationSecurityMetadataSource)RequestMatcherDelegatingAuthorizationManager 将 URL 映射到授权规则
配置方式HttpSecurity.authorizeRequests()(链式配置)HttpSecurity.authorizeHttpRequests()(Lambda DSL)
扩展方式自定义 AccessDecisionManager、SecurityMetadataSource自定义 AuthorizationManager

Q2:FilterSecurityInterceptor 的 invoke 方法中,beforeInvocation 和 afterInvocation 分别做什么?

FilterSecurityInterceptor 的 invoke 方法继承自 AbstractSecurityInterceptor,其逻辑如下:

public void invoke(FilterInvocation fi) throws IOException, ServletException {
    // 1. beforeInvocation:执行授权决策
    InterceptorStatusToken token = super.beforeInvocation(fi);
    
    try {
        // 2. 执行目标 FilterChain(请求继续到达 Controller)
        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    } finally {
        // 3. finallyInvocation:清理资源(如 RunAsManager 的临时身份)
        super.finallyInvocation(token);
    }
    
    // 4. afterInvocation:在请求完成后执行
    super.afterInvocation(token, null);
}
  • beforeInvocation:

    • 调用 SecurityMetadataSource.getAttributes() 获取 ConfigAttribute 列表。
    • 调用 AccessDecisionManager.decide() 进行授权投票。
    • 如果认证失败或未认证,抛出 AccessDeniedException 或 AuthenticationException。
    • 如果检查通过,返回 InterceptorStatusToken,用于后续的 afterInvocation。
  • afterInvocation:

    • 在 FilterChain 执行完成后调用。
    • 在 URL 级别 的 FilterSecurityInterceptor 中,afterInvocation 通常不做任何事(URL 级别没有返回值可以过滤)。
    • 在 方法级别 的 MethodSecurityInterceptor 中,afterInvocation 会调用 AfterInvocationProvider(如 @PostFilter 的权限过滤逻辑),对方法的返回值进行后处理。

因此,beforeInvocation 是"准入控制",afterInvocation 是"后处理控制"。对于 FilterSecurityInterceptor,beforeInvocation 是核心,afterInvocation 在标准用法中基本为空操作。

上一页
ExceptionTranslationFilter