You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

返回CompletableFuture<ResponseEntity<ExecutionDto>>时始终报403错误排查

问题分析与解决方案

核心原因

Spring Security的SecurityContext默认绑定到当前线程的ThreadLocal中,使用CompletableFuture异步处理时,任务会在新线程执行,原线程的安全上下文不会自动传递,导致异步流程中权限校验失败返回403。即便在服务层手动设置安全上下文,若传递时机或方式不当,也无法解决问题。

可行解决方案

方案1:全局配置可继承的安全上下文策略

在项目配置类或启动类中设置安全上下文为可继承模式,让异步子线程自动继承主线程的安全上下文:

@Configuration
public class SecurityConfig {
    static {
        // 开启上下文继承,异步线程可获取主线程的安全上下文
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }
}

方案2:手动传递安全上下文到异步任务

如果不想全局修改策略,可在控制器中手动捕获当前安全上下文,在异步任务执行时注入:

@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public CompletableFuture<ResponseEntity<ExecutionDto>> handleFileUpload(
        @RequestPart("file1") MultipartFile file1,
        @RequestPart(value = "file2", required = false) MultipartFile file2) {

    List<MultipartFile> files = new ArrayList<>();
    files.add(file1);
    if (file2 != null && !file2.isEmpty()) {
        files.add(file2);
    }

    // 主线程中获取用户与安全上下文
    User user = userRepository.findByEmailWithAgency(userService.getAuthenticatedUser().getEmail()).get();
    SecurityContext currentContext = SecurityContextHolder.getContext();

    String uploadId = "upload_" + System.currentTimeMillis();
    System.out.println("Upload ID : " + uploadId);
    progressTracker.initializeProgress(uploadId);

    // 异步任务中手动注入安全上下文,执行后清空避免线程污染
    return CompletableFuture.supplyAsync(() -> {
        SecurityContextHolder.setContext(currentContext);
        try {
            return executionService.handleFileUpload(files, uploadId, user);
        } finally {
            SecurityContextHolder.clearContext();
        }
    }).thenApply(ResponseEntity::ok);
}

方案3:使用Spring @Async并配置带上下文传递的异步执行器

将服务层方法改为异步执行,同时配置任务装饰器自动传递安全上下文:

  1. 服务层方法添加@Async注解:
@Service
public class ExecutionService {
    @Async
    public CompletableFuture<ExecutionDto> handleFileUpload(List<MultipartFile> files, String uploadId, User user) {
        // 业务逻辑实现
        return CompletableFuture.completedFuture(new ExecutionDto());
    }
}
  1. 配置异步执行器:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(20);
        executor.setThreadNamePrefix("UploadAsync-");
        // 任务装饰器:传递主线程安全上下文到异步线程
        executor.setTaskDecorator(runnable -> {
            SecurityContext context = SecurityContextHolder.getContext();
            return () -> {
                try {
                    SecurityContextHolder.setContext(context);
                    runnable.run();
                } finally {
                    SecurityContextHolder.clearContext();
                }
            };
        });
        executor.initialize();
        return executor;
    }
}

注意事项

  • 无论使用哪种方案,都要确保异步任务执行完毕后清空安全上下文,避免线程池复用线程时携带旧上下文引发权限问题。
  • 检查项目中自定义的拦截器、过滤器,确认异步场景下的权限校验逻辑无异常。

内容的提问来源于stack exchange,提问作者Maro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 17:43:10