SecurityFilterChain 过滤 2 次并失去身份验证

问题描述 投票:0回答:1

在异步之前一切正常。所以问题是 securityFilterChain 过滤了 2 次,我认为它应该只过滤一次,因为过滤 2 次没有意义,即使过滤 2 次是正确的,它也会丢失抛出 AuthenticationEntryPoint 的身份验证。我将在这里粘贴一些代码垃圾以及控制台中的一些调试打印。

    @Bean("AsyncTask")
    @Primary
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(100);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("template-thread-");
        executor.initialize();
        return new DelegatingSecurityContextAsyncTaskExecutor(executor);
    }
    @GetMapping("/get_test")
    public CompletableFuture<String> getTest() throws InterruptedException {

        System.out.println("Entered in controller");

        long i = Thread.currentThread().getId();
        System.out.println("Thread id: " + i);

        SecurityContext preContext = SecurityContextHolder.getContext();
        System.out.println("Before Async - Authenticated user: " + (preContext.getAuthentication() != null ? preContext.getAuthentication().getName() : "null"));

        return testService.asyncMethod();
    }
    @Async("AsyncTask")
    public CompletableFuture<String> asyncMethod() throws InterruptedException {

        System.out.println("Entered in service");

        System.out.println("Thread id: " + Thread.currentThread().getId());

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("In async method - Authenticated user: " + (auth != null ? auth.getName() : "none"));

        return CompletableFuture.completedFuture("Hello World");
    }
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig{

    private final JWTAuthenticationFilter jwtAuthenticationFilter;
    private final PermissionMapping permissionMapping;
    private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
    private final CustomAccessDeniedHandler customAccessDeniedHandler;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{
        httpSecurity
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

                .authorizeHttpRequests(auth -> auth
                        .anyRequest().access((authenticationSupplier, context) -> {
                            System.out.println("Entered in security filter");
                            Authentication authentication = authenticationSupplier.get();


                            System.out.println("Thread id: " + Thread.currentThread().getId());
                            System.out.println("Security - Authenticated user: " + authentication.getName());

                            String currentUri = context.getRequest().getRequestURI();
                            System.out.println("Security - Current URI: " + currentUri);
                            List<String> requiredPermissions = permissionMapping.getPermissions(currentUri);

                            if (requiredPermissions != null) {
                                boolean hasPermission = authentication.getAuthorities().stream()
                                        .anyMatch(grantedAuthority -> requiredPermissions.contains(grantedAuthority.getAuthority()));

                                return new AuthorizationDecision(hasPermission);
                            }
                            return new AuthorizationDecision(true);
                        })
                )

                .exceptionHandling(exceptionHandling -> exceptionHandling
                        .authenticationEntryPoint(customAuthenticationEntryPoint) // Handle unauthenticated access
                        .accessDeniedHandler(customAccessDeniedHandler) // Handle unauthorized access
                );

        return httpSecurity.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

打印:
已输入 JWT 过滤器
主题 ID:42
已进入安全过滤器
主题 ID:42
安全性 - 经过身份验证的用户:dev
安全性 - 当前 URI:/get_test
已输入控制器
主题 ID:42
异步之前 - 经过身份验证的用户:dev
已投入使用
主题 ID:66
在异步方法中 - 经过身份验证的用户:dev
已进入安全过滤器
主题 ID:42
安全性 - 经过身份验证的用户:anonymousUser
安全性 - 当前 URI:/get_test

我尝试更改上下文持有者的策略:SecurityContextHolder.MODE_INHERITABLETHREADLOCAL,但这也不起作用。

spring-boot spring-mvc asynchronous spring-security security-context
1个回答
0
投票

我认为这是因为 SecurityContext 的范围默认是 ThreadLocal ,但是当您使用 @Async 时,它将在新线程中运行此方法,并且您的 securityContext 不会在此请求中携带,为此您可以覆盖 ThreadLocal 的默认属性并使用 INHERITABLETHREADLOCAL 会将您的安全上下文带到新线程
注意: INHERITABLETHREADLOCAL 只会在 spring 创建的线程中携带 securityContext,例如 @Async ,如果您使用手动创建的 ExecutorService ,那么它将不起作用,您必须使用 DelegatingSecurityContextRunnable , DelegatingSecurityContextCallable 或类似的东西。

这就是如何使用 INHERITABLETHREADLOCAL :

@Configuration
@EnableAsync
public class ProjectConfig {
 @Bean
 public InitializingBean initializingBean() {
 return () -> SecurityContextHolder.setStrategyName(
 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
 }
© www.soinside.com 2019 - 2024. All rights reserved.