Spring Security и @Async (аутентифицированные пользователи перепутали) - PullRequest
30 голосов
/ 09 марта 2011

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

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

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

Ответы [ 5 ]

34 голосов
/ 09 марта 2011

Я думаю, 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();
            }
        }
    });
}
12 голосов
/ 19 февраля 2015

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

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

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

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

6 голосов
/ 06 января 2016

Использование информации от Ральфа и Оака -

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

1 голос
/ 27 апреля 2015

На основе ответа @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>
0 голосов
/ 06 января 2017

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);
        }
    }
...