Подтвердить что ты не робот

Spring Security и @Async (аутентифицированные пользователи перепутали)

Я асинхронно вызываю метод в Spring, используя @Async.Этот метод вызывает другой метод, помеченный @PreAuthorize, Spring Security Annotation. Чтобы авторизация MODE_INHERITABLETHREADLOCAL, мне нужно установить режим SecurityContextHolder в MODE_INHERITABLETHREADLOCAL, чтобы информация аутентификации передавалась асинхронному вызову. Пока все отлично работает.

Однако, когда я выхожу из системы и входю в систему как другой пользователь, в асинхронном методе SecurityContextHolder хранит информацию аутентификации старого пользователя, который вышел из системы. Это вызывает, конечно, нежелательное исключение AccessDenied. Нет такой проблемы с синхронными вызовами.

Я определил <task:executor id="executors" pool-size="10"/>, поэтому может быть проблема в том, что после инициализации потока в пуле исполнителей он не будет переопределять информацию аутентификации?

4b9b3361

Ответ 1

Я думаю, MODE_INHERITABLETHREADLOCAL работает неправильно с пулом потоков.

В качестве возможного решения вы можете попробовать подкласс ThreadPoolTaskExecutor и переопределить его методы для распространения 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();
            }
        }
    });
}

Ответ 2

Это всего лишь намек, который нуждается в будущем исследовании (я слишком устал, но, возможно, кто-то найдет это полезным для будущего исследования):

Сегодня я наткнулся на org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor, см. GitHub.

похоже, что он предназначен для делегирования контекста безопасности, чтобы он "прошел" через вызов @Async.

Также посмотрите этот пост: Spring Безопасность 3.2. Основные моменты M1, поддержка API Servlet 3 - это звучит так, как будто это сильно связано.

Ответ 3

Использование информации от Ralph and Oak -

Если вы хотите, чтобы @Async работал со стандартным тегом исполнителя задачи, вы бы настроили свой XML-конфигуратор Spring следующим образом

<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, поток threadpool будет использовать тот же контекст безопасности, что и вызывающий поток

Ответ 4

На основе ответа @Ralph можно достичь Aync event с помощью Spring с помощью threadpooling и делегировать безопасность, используя http://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>

Ответ 5

Jus, чтобы добавить к ответу @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);
        }
    }