programing

Spring Security 및 @Async(인증된 사용자 혼합)

goodsources 2023. 11. 7. 20:48
반응형

Spring Security 및 @Async(인증된 사용자 혼합)

나는 스프링과 함께 비동기적으로 메소드를 호출합니다.@Async. 이 메서드는 다음과 같이 주석이 달린 다른 메서드를 호출합니다.@PreAuthorize, 춘계 보안 주석인증 작업을 수행하려면 설정해야 합니다.SecurityContextHolder에의 모드MODE_INHERITABLETHREADLOCAL, 인증 정보가 비동기 호출로 전달되도록 합니다.지금까지는 모든 것이 잘 됩니다.

그러나 로그아웃하고 다른 사용자로 로그인하면 비동기 방식으로 SecurityContextHolder가 로그아웃한 이전 사용자의 인증 정보를 저장합니다.물론 원치 않는 원인이 됩니다.AccessDenied예외.동기 호출에는 그런 문제가 없습니다.

정의했습니다.<task:executor id="executors" pool-size="10"/>, 그래서 실행자 풀의 스레드가 초기화되면 인증 정보를 덮어쓰지 않는 것이 문제가 될 수 있습니까?

그런 것 같다.MODE_INHERITABLETHREADLOCAL스레드 풀에서 올바르게 작동하지 않습니다.

가능한 해결책으로 서브클래스를 시도하고 전파할 메서드를 재정의할 수 있습니다.SecurityContext수동으로 실행자를 선언합니다.<task:executor>, 다음과 같은 것.

public void execute(final Runnable r) {
    final Authentication a = SecurityContextHolder.getContext().getAuthentication();

    super.execute(new Runnable() {
        public void run() {
            try {
                SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                ctx.setAuthentication(a);
                SecurityContextHolder.setContext(ctx);
                r.run();
            } finally {
                SecurityContextHolder.clearContext();
            }
        }
    });
}

저도 그 문제에 부딪혔습니다.ThreadPoolTask를 구성하는 것이 중요합니다.실행자가 올바르게 사용합니다.DelegatingSecurityContextAsyncTaskExecutor. 또한 initialize() 메서드를 호출하는 것이 중요합니다. 그렇지 않으면 오류가 발생합니다.

@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(20);
    executor.setMaxPoolSize(1000);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setThreadNamePrefix("Async-");
    executor.initialize(); // this is important, otherwise an error is thrown
    return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}

비즈니스 로직에서 비동기적으로 호출되는 메소드:

@Override
@Async("threadPoolTaskExecutor")
public void methodName() {
    [..]
}


업데이트 - 기타 접근 방식 - 자세한 정보:보안 컨텍스트(즉, 인증 정보)만 위임하려는 경우에는 위에서 설명한 솔루션이 완벽하게 작동합니다.

그러나 특정 상황에서는 MDC 컨텍스트나 요청 컨텍스트와 같은 다른 스레드 정보를 위임하거나 실행자 스레드로 전달되는 방법을 더 많이 제어하기를 원할 수도 있습니다.이 경우 로컬 스레드 정보를 실행자 스레드에 수동으로 바인딩할 수 있습니다.이 작업을 수행할 수 있는 방법에 대해서는 @axtavt의 답변에서 이미 설명되어 있지만 이제 작업 데코레이터를 사용하여 보다 우아한 방식으로 작업을 수행할 수 있습니다.TaskDecorator는 요청 스레드의 컨텍스트를 변수로 저장하고 해당 컨텍스트를 실행자 스레드에서 액세스할 수 있도록 클로징으로 바인딩합니다.스레드 실행이 완료되면 실행자 스레드에서 컨텍스트가 지워져서 스레드가 재사용될 때 정보가 사라집니다.

private static class ContextCopyingDecorator implements TaskDecorator {
    @NonNull
    @Override
    public Runnable decorate(@NonNull Runnable runnable) {
        // store context in variables which will be bound to the executor thread
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        SecurityContext securityContext = SecurityContextHolder.getContext();
        Map<String, String> mdcContextMap = MDC.getCopyOfContextMap();
        return () -> { // code runs inside executor thread and binds context
            try {
                if (requestAttributes != null) {
                    RequestContextHolder.setRequestAttributes(requestAttributes);
                }
                if (securityContext != null) {
                    SecurityContextHolder.setContext(securityContext);
                }
                if (mdcContextMap != null) {
                    MDC.setContextMap(mdcContextMap);
                }
                runnable.run();
            } finally {
                MDC.clear();
                RequestContextHolder.resetRequestAttributes();
                SecurityContextHolder.clearContext();
            }
        };
    }
}

TaskExecutor를 생성하는 동안 TaskDecorator는 설정된 TaskDecorator 메서드와 함께 TaskExecutor에 할당됩니다.

@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(20);
    executor.setMaxPoolSize(1000);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setThreadNamePrefix("Async-");
    executor.setTaskDecorator(new ContextCopyingDecorator());
    return executor;
}

또한 두 가지 접근 방식을 모두 결합할 수도 있습니다(예: TaskDecorator를 사용하여 MDC 컨텍스트를 복사하고 DelegatingSecurityContextAsyncTask를 사용하는 경우).보안 컨텍스트의 경우 실행자)를 사용하지만 복잡성이 증가하기 때문에 이 작업을 권장하지는 않습니다.TaskDecorator를 사용하는 경우 보안 컨텍스트를 함께 설정할 수도 있으며 보안 컨텍스트만 설정하면 되는 경우 DelegatingSecurityContextAsyncTask를 사용하면 됩니다.실행자 접근.

이는 향후 조사가 필요한 힌트일 뿐입니다. (저는 너무 피곤하지만 누군가가 향후 조사에 유용하다고 생각할 수도 있습니다.)

오늘 우연히 깃허브를 보게 되었습니다.

보안 컨텍스트를 위임하여 보안 컨텍스트를 "통과"하도록 설계된 것으로 보입니다.@Async불러.

또한 이 게시물을 보세요: 스프링 시큐리티 3.2 M1 하이라이트, 서블릿 3 API 지원은 강력한 관련이 있는 것처럼 들립니다.

랄프와 오크의 정보를 이용해서...

@Async가 표준 작업 실행자 태그와 함께 작동하도록 하려면 다음과 같이 Spring XML 구성을 설정합니다.

<task:annotation-driven executor="_importPool"/>
<task:executor id="_importPool" pool-size="5"/>

<bean id="importPool"
          class="org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor">
     <constructor-arg ref="_importPool"/>
</bean>

그런 다음 @Async 메서드에서 사용할 풀을 지정합니다.

@Async("importPool")
public void run(ImportJob import) {
   ...
}

@Async 메서드를 호출할 때마다 스레드 풀 스레드는 호출 스레드와 동일한 보안 컨텍스트를 사용합니다.

이미 언급했듯이 풀링된 스레드 환경의 경우DelegatingSecurityContextAsyncTaskExecutor대신에 사용되어야 합니다.MODE_INHERITABLETHREADLOCAL(여기 읽기).

단순하게 남기기DelegatingSecurityContextAsyncTaskExecutor비동기 작업을 위해 기본 Spring Boot 풀만 사용하는 Spring Boot 프로젝트에 대한 구성:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    private final ThreadPoolTaskExecutor defaultSpringBootAsyncExecutor;

    public AsyncConfig(ThreadPoolTaskExecutor defaultSpringBootAsyncExecutor) {
        this.defaultSpringBootAsyncExecutor = defaultSpringBootAsyncExecutor;
    }

    @Override
    public Executor getAsyncExecutor() {
        return new DelegatingSecurityContextAsyncTaskExecutor(defaultSpringBootAsyncExecutor);
    }
}

@Ralph 답변을 바탕으로 달성할 수 있습니다.Aync event와 함께Spring와 함께threadpoolinghttp://docs.spring.io/autorepo/docs/spring-security/4.0.0.M1/apidocs/org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutor.html 을 사용하여 보안을 위임합니다.

샘플코드

<bean id="applicationEventMulticaster"
    class="org.springframework.context.event.SimpleApplicationEventMulticaster">
    <property name="taskExecutor">
        <ref bean="delegateSecurityAsyncThreadPool"/>
    </property>
</bean>

<bean id="threadsPool"
    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
</bean>


<bean id="delegateSecurityAsyncThreadPool"
    class="org.springframework.security.task.DelegatingSecurityContextTaskExecutor">
    <constructor-arg ref="threadsPool"/>
</bean>

@axtavt의 답변에 추가하기 위해 다른 메소드도 재정의해야 합니다.

@Override
    public <T> Future<T> submit(Callable<T> task) {
        ExecutorService executor = getThreadPoolExecutor();
        final Authentication a = SecurityContextHolder.getContext().getAuthentication();
        try {
            return executor.submit(new Callable<T>() {
                @Override
                public T call() throws Exception {
                    try {
                        SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                        ctx.setAuthentication(a);
                        SecurityContextHolder.setContext(ctx);
                        return task.call();
                    } catch (Exception e) {
                        slf4jLogger.error("error invoking async thread. error details : {}", e);
                        return null;
                    } finally {
                        SecurityContextHolder.clearContext();
                    }
                }
            });
        } catch (RejectedExecutionException ex) {
            throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
        }
    }

JNDI 관리 스레드 풀이 있는 Enterprise Jboss Server를 사용합니다.이것이 제게 효과가 있었던 것입니다.

@Configuration
@EnableAsync
public class AsyncAppConfig {

    public static final String THREAD_POOL_ID = "threadPoolId";

    @Bean(THREAD_POOL_ID)
    public DelegatingSecurityContextAsyncTaskExecutor secureThreadPool(
            DefaultManagedTaskExecutor jbossManagedTaskExecutor) {
        return new DelegatingSecurityContextAsyncTaskExecutor(jbossManagedTaskExecutor);
    }

    @Bean
    public DefaultManagedTaskExecutor jbossManagedTaskExecutor() {
        return new DefaultManagedTaskExecutor() {
            @Override
            public void afterPropertiesSet() throws NamingException {
                // gets the ConcurrentExecutor configured by Jboss
                super.afterPropertiesSet();
                // Wraps the jboss configured ConcurrentExecutor in a securityContext aware delegate
                setConcurrentExecutor(new DelegatingSecurityContextExecutor(getConcurrentExecutor(), getContext()));
            }
        };
    }
}

Spring security 6을 사용하면 명시적인 컨텍스트 저장을 비활성화해야 했습니다.websecurityConfig에서 아래와 같이 했습니다.

코틀린에 대한 토막글입니다.

@Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        return http
            .securityContext().requireExplicitSave(false).and().....

언급URL : https://stackoverflow.com/questions/5246428/spring-security-and-async-authenticated-users-mixed-up

반응형