Spring Security и @Async (аутентифицированные пользователи перепутали)
Я асинхронно вызываю метод в Spring, используя @Async.Этот метод вызывает другой метод, помеченный @PreAuthorize, Spring Security Annotation. Чтобы авторизация MODE_INHERITABLETHREADLOCAL
, мне нужно установить режим SecurityContextHolder
в MODE_INHERITABLETHREADLOCAL
, чтобы информация аутентификации передавалась асинхронному вызову. Пока все отлично работает.
Однако, когда я выхожу из системы и входю в систему как другой пользователь, в асинхронном методе SecurityContextHolder хранит информацию аутентификации старого пользователя, который вышел из системы. Это вызывает, конечно, нежелательное исключение AccessDenied
. Нет такой проблемы с синхронными вызовами.
Я определил <task:executor id="executors" pool-size="10"/>
, поэтому может быть проблема в том, что после инициализации потока в пуле исполнителей он не будет переопределять информацию аутентификации?
Ответы
Ответ 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);
}
}
Ответ 6
Отличным способом является определение вашего TaskExecutor с использованием DelegatingSecurityContextExecutorService: https://spring.io/guides/topicals/spring-security-architecture/#_processing_secure_methods_asynchronously