Статьи

Весна из окопов: вызов защищенного метода из запланированной работы

Давайте предположим, что мы реализовали приложение на базе Spring и защитили его с помощью выражений безопасности метода Spring Security .

Наша следующая задача — реализовать запланированную работу с использованием защищенных методов. Чтобы быть более конкретным, мы должны реализовать запланированное задание, которое получает сообщение от нашего класса обслуживания и записывает полученное сообщение в журнал.

Давайте начнем.

Запланированные задания, описанные в этой записи блога, используют выражения cron, которые настраиваются в файлах конфигурации конкретного профиля. Если вы не знаете, как это сделать, я рекомендую вам прочитать мой пост в блоге, в котором описано, как вы можете использовать специфичные для среды выражения cron с аннотацией @Scheduled .

Наша первая попытка

Давайте создадим запланированное задание, которое вызовет защищенный метод и выясним, что происходит при выполнении задания. Давайте начнем с рассмотрения сервисного уровня нашего примера приложения.

Уровень обслуживания

Методы защищенного класса обслуживания объявлены в интерфейсе MessageService . Он объявляет один метод с именем getMessage () и указывает, что его могут вызывать только пользователи с ролью ROLE_USER .

Исходный код интерфейса MessageService выглядит следующим образом:

1
2
3
4
5
6
7
import org.springframework.security.access.prepost.PreAuthorize;
 
public interface MessageService {
 
    @PreAuthorize("hasRole('ROLE_USER')")
    public String getMessage();
}

Наша реализация интерфейса MessageService довольно проста. Его исходный код выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
import org.springframework.stereotype.Service;
 
@Service
public class HelloMessageService implements MessageService {
 
    @Override
    public String getMessage() {
        return "Hello World!";
    }
}

Давайте продолжим и создадим запланированное задание, которое вызывает метод getMessage ().

Создание запланированного задания

Мы можем создать запланированное задание, выполнив следующие действия:

  1. Создайте класс ScheduledJob и аннотируйте его аннотацией @Component . Это гарантирует, что наше запланированное задание будет найдено во время сканирования пути к классу (если мы поместим его в проверяемый пакет).
  2. Добавьте личное поле Logger в созданный класс и создайте объект Logger , вызвав статический метод getLogger () класса LoggerFactory . Мы будем использовать объект Logger для записи сообщения, которое мы получаем от объекта HelloMessageService, в журнал.
  3. Добавьте личное поле MessageService в созданный класс.
  4. Добавьте конструктор к созданному классу и аннотируйте его аннотацией @Autowired . Это гарантирует, что мы можем внедрить bean- компонент MessageService в поле MessageService , используя внедрение конструктора.
  5. Добавьте публичный метод run () к созданному классу и аннотируйте его аннотацией @Scheduled . Установите значение его атрибута cron равным «$ {scheduling.job.cron}» . Это означает, что выражение cron читается из файла свойств, а его значением является значение свойства scheduling.job.cron ( более подробно об этом см. В этом сообщении в блоге ).
  6. Реализуйте метод run () , вызвав метод getMessage () интерфейса MessageService . Запишите полученное сообщение в журнал.

Исходный код нашей запланированной работы выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
@Component
public class ScheduledJob {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);
 
    private final MessageService messageService;
 
    @Autowired
    public ScheduledJob(MessageService messageService) {
        this.messageService = messageService;
    }
 
    @Scheduled(cron = "${scheduling.job.cron}")
    public void run() {
        String message = messageService.getMessage();
        LOGGER.debug("Received message: {}", message);
    }
}

Давайте посмотрим, что происходит, когда вызывается метод run () класса ScheduledJob .

Не работает

Когда наше запланированное задание выполняется, генерируется исключение AuthenticationCredentialsNotFoundException, и мы видим следующую трассировку стека:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2013-12-10 19:45:19,001 ERROR - kUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task.
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:339)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:198)
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at com.sun.proxy.$Proxy31.getMessage(Unknown Source)
    at net.petrikainulainen.spring.trenches.scheduling.job.ScheduledJobTwo.run(ScheduledJobTwo.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:64)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:53)
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
    at java.util.concurrent.FutureTask.run(FutureTask.java:166)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:722)

Эта трассировка стека на самом деле очень полезна. Он говорит нам, что безопасный метод не может быть вызван, потому что объект Security Authentication не был найден из SecurityContext .

Два наиболее распространенных решения этой проблемы, которые я видел:

  • Создайте отдельный метод, который делает то же самое, что и защищенный метод, и измените запланированное задание, чтобы использовать этот метод. Этот метод часто имеет комментарий Javadoc, в котором говорится, что только запланированное задание может вызывать этот метод. У этого решения есть две проблемы: 1) оно загромождает кодовую базу и 2) кто-то в конечном итоге вызовет этот метод в любом случае (никто действительно не читает Javadocs, если не обязан)
  • Удалите аннотацию безопасности метода из метода, вызванного запланированным заданием. Это действительно плохое решение по понятным причинам. Подсказка: этот метод был обеспечен по уважительной причине!

К счастью, есть и третий способ решения этой проблемы. Давайте начнем с выяснения, где хранится контекст безопасности, используемый нашей запланированной работой.

Откуда берется контекст безопасности?

Решение нашей проблемы очевидно: нам нужно создать объект аутентификации и добавить его в SecurityContext до вызова защищенного метода.

Однако прежде чем мы сможем внести необходимые изменения в наше примерное приложение, мы должны понять, где хранится объект SecurityContext .

Если мы не настроили иначе, контекст безопасности сохраняется в ThreadLocal . Другими словами, каждый поток имеет свой собственный контекст безопасности. Это означает, что все запланированные задания, которые выполняются в одном потоке, используют один и тот же контекст безопасности.

Давайте предположим, что у нас есть три запланированных рабочих места. Эти задания называются A , B и C. Также предположим, что эти задания выполняются в алфавитном порядке.

Если мы используем пул потоков по умолчанию, который имеет только один поток, все задания имеют одинаковый контекст безопасности. Если задание B устанавливает объект аутентификации в контексте безопасности, при выполнении запланированных заданий происходит следующее:

  • Задание A не может вызвать защищенный метод, потому что оно выполняется перед заданием B. Это означает, что объект аутентификации не найден в контексте безопасности.
  • Задание B может вызвать защищенный метод, поскольку оно устанавливает объект Authentication в контекст безопасности, прежде чем попытаться вызвать защищенный метод.
  • Задание C может вызывать защищенный метод, потому что он выполняется после задания B, которое устанавливает объект Authentication в контекст безопасности.

Если мы используем пул потоков, который имеет более одного потока, каждый поток имеет свой собственный контекст безопасности. Если задание A устанавливает объект Аутентификация в контексте безопасности, все задания, которые выполняются в одном и том же потоке, выполняются с использованием тех же привилегий, что и после задания A.

Давайте рассмотрим каждую работу по очереди:

  • Задание A может вызвать защищенный метод, поскольку оно устанавливает объект Authentication в контекст безопасности, прежде чем попытаться вызвать защищенный метод.
  • Задание B может вызывать защищенный метод, если он выполняется в том же потоке, что и задание A. Если задание не выполняется в том же потоке, оно не может вызвать защищенный метод, поскольку объект аутентификации не найден в контексте безопасности.
  • Задание C может вызывать защищенный метод, если он выполняется в том же потоке, что и задание A. Если задание не выполняется в том же потоке, оно не может вызвать защищенный метод, поскольку объект аутентификации не найден в контексте безопасности.

Ясно, что лучший способ решить эту проблему — убедиться, что каждое запланированное задание выполняется с использованием необходимых привилегий. Это решение имеет два преимущества:

  • Мы можем выполнить нашу работу в любом порядке.
  • Мы не должны гарантировать, что задания выполняются в «правильном» потоке.

Давайте выясним, как мы можем решить эту проблему, когда наше приложение использует Spring Security 3.1.

Spring Security 3.1: требуется ручная работа

Если наше приложение использует Spring Security 3.1, самый простой способ решить нашу проблему

  • Создайте объект аутентификации и установите его в контексте безопасности, прежде чем наша работа попытается вызвать защищенный метод.
  • Удалите объект аутентификации из контекста безопасности до завершения задания.

Давайте начнем с создания класса AuthenticationUtil, который предоставляет необходимые методы.

Создание класса AuthenticationUtil

Мы можем создать класс AuthenticationUtil , выполнив следующие действия:

  1. Создайте класс AuthenticationUtil .
  2. Добавьте приватный конструктор класс AuthenticationUtil . Это гарантирует, что класс не может быть создан.
  3. Добавьте статический метод clearAuthentication () в класс и реализуйте метод, выполнив следующие действия:
    1. Получите объект SecurityContext , вызвав статический метод getContext () класса SecurityContextHolder .
    2. Удалите информацию аутентификации, вызвав метод setContext () интерфейса SecurityContext . Передайте значение null в качестве параметра метода.
  4. Добавьте статический метод configureAuthentication () в класс. Этот метод принимает роль пользователя в качестве параметра метода. Реализуйте этот метод, выполнив следующие действия:
    1. Создайте коллекцию объектов GrantedAuthority , вызвав статический метод createAuthorityList () класса AuthorityUtils . Передайте роль пользователя в качестве параметра метода.
    2. Создайте новый объект UsernamePasswordAuthenticationToken и передайте следующие объекты в качестве аргументов конструктора:
      1. Первый аргумент конструктора является основным. Передайте строку ‘user’ в качестве первого аргумента конструктора.
      2. Второй аргумент конструктора — учетные данные пользователя. Передайте роль, заданную в качестве параметра метода, в качестве второго аргумента конструктора.
      3. Третий аргумент конструктора содержит полномочия пользователя. Передайте созданный объект Collection <GrantedAuthority> в качестве третьего аргумента конструктора.
    3. Получите объект SecurityContext , вызвав статический метод getContext () класса SecurityContextHolder .
    4. Установите созданный объект аутентификации в контекст безопасности, вызвав метод setAuthentication () интерфейса SecurityContext . Передайте созданный UsernamePasswordAuthenticationToken в качестве параметра метода.

Исходный код класса AuthenticationUtil выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
 
import java.util.Collection;
 
public final class AuthenticationUtil {
 
    //Ensures that this class cannot be instantiated
    private AuthenticationUtil() {
    }
 
    public static void clearAuthentication() {
        SecurityContextHolder.getContext().setAuthentication(null);
    }
 
    public static void configureAuthentication(String role) {
        Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(role);
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                "user",
                role,
                authorities
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}

Мы еще не закончили. Нам еще предстоит внести некоторые изменения в нашу запланированную работу. Давайте выясним, как мы можем сделать эти модификации.

Изменение запланированного задания

Мы должны сделать две модификации в классе ScheduledJob . Мы можем внести эти изменения, выполнив следующие действия:

  1. Вызвать статический метод configureAuthentication () класса AuthenticationUtil при запуске задания и передать строку «ROLE_USER» в качестве параметра метода. Это гарантирует, что наше запланированное задание может выполнять те же методы, что и обычный пользователь с ролью ROLE_USER .
  2. Вызовите статический метод clearAuthentication () класса AuthenticationUtil непосредственно перед завершением задания. Это удалило информацию аутентификации из контекста безопасности.

Исходный код класса ScheduledJob выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
@Component
public class ScheduledJob {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);
 
    private final MessageService messageService;
 
    @Autowired
    public ScheduledJob(MessageService messageService) {
        this.messageService = messageService;
    }
 
    @Scheduled(cron = "${scheduling.job.cron}")
    public void run() {
        AuthenticationUtil.configureAuthentication("ROLE_USER");
 
        String message = messageService.getMessage();
        LOGGER.debug("Received message: {}", message);
 
        AuthenticationUtil.clearAuthentication();
    }
}

Давайте выясним, что происходит, когда запускается наша запланированная работа.

Выполнение запланированного задания

Когда задание вызывается, в журнал записывается следующее сообщение:

1
2013-12-17 20:41:33,019 DEBUG - ScheduledJob            - Received message: Hello World!

Все работает отлично, когда наше приложение использует Spring Security 3.1. Наше решение не такое элегантное, но оно работает. Очевидным недостатком этого решения является то, что мы должны помнить, чтобы вызывать методы configureAuthentication () и clearAuthentication () класса AuthenticationUtil в наших запланированных заданиях.

Spring Security 3.2 решает эту проблему. Давайте продолжим и выясним, как мы можем решить эту проблему, когда наше приложение использует Spring Security 3.2.

Spring Security 3.2: это почти как волшебство!

Spring Security 3.2 имеет совершенно новую поддержку параллелизма, которая дает нам возможность переносить контекст безопасности из одного потока в другой. Давайте выясним, как мы можем настроить контекст нашего приложения для использования функций, предоставляемых Spring Security 3.2.

Настройка контекста приложения

Поскольку мы хотим использовать новую поддержку параллелизма Spring Security 3.2, мы должны внести следующие изменения в наш класс конфигурации контекста приложения ( исходная конфигурация описана в этом сообщении в блоге ):

  1. Реализуйте интерфейс SchedulingConfigurer . Этот интерфейс может быть реализован с помощью классов конфигурации контекста приложения, которые аннотируются аннотацией @EnableScheduling , и его часто используют для настройки используемого компонента TaskScheduler или программной настройки выполненных задач.
  2. Добавьте частный метод createrSchedulerSecurityContext () в класс конфигурации. Этот метод не имеет параметров метода, он возвращает объект SecurityContext . Реализуйте этот метод, выполнив следующие действия:
    1. Создайте новый объект SecurityContext , вызвав статический метод createEmptyContext () класса SecurityContextHolder .
    2. Создайте коллекцию объектов GrantedAuthority , вызвав статический метод createAuthorityList () класса AuthorityUtils . Передайте строку ‘ROLE_USER’ в качестве параметра метода.
    3. Создайте новый объект UsernamePasswordAuthenticationToken и передайте следующие объекты в качестве аргументов конструктора:
      1. Первый аргумент конструктора является основным. Передайте строку ‘user’ в качестве первого аргумента конструктора.
      2. Второй аргумент конструктора — учетные данные пользователя. Передайте строку ‘ROLE_USER’ в качестве второго аргумента конструктора.
      3. Третий аргумент конструктора содержит полномочия пользователя. Передайте созданный объект Collection <GrantedAuthority> в качестве третьего аргумента конструктора.
    4. Установите для созданного объекта UsernamePasswordAuthenticationToken созданный контекст безопасности, вызвав метод setAuthentication () интерфейса SecurityContext .
  3. Добавьте открытый метод taskExecutor () в класс конфигурации и аннотируйте метод аннотацией @Bean . Этот метод не имеет параметров метода и возвращает объект Executor . Реализуйте этот метод, выполнив следующие действия:
    1. Создайте новый объект ScheduledExecutorService , вызвав статический метод newSingleThreadScheduledExecutor () класса Executors . Это создает объект ScheduledExecutorService, который выполняет все задания с использованием одного потока.
    2. Получите ссылку на объект SecurityContext , вызвав закрытый метод createSchedulerSecurityContext () .
    3. Создайте новый объект DelegatingSecurityContextScheduledExecutorService и передайте следующие объекты в качестве аргументов конструктора:
      1. Первый аргумент конструктора — это объект ScheduledExecutorService . Этот объект используется для вызова запланированных заданий. Передайте созданный объект ScheduledExecutorService в качестве первого аргумента конструктора.
      2. Второй аргумент конструктора — это объект SecurityContext . Созданный объект DelegatingSecurityContextScheduledExecutorService гарантирует, что каждое вызванное задание использует этот SecurityContext . Передайте созданный объект SecurityContext в качестве второго аргумента конструктора.
    4. Вернуть созданный объект DelegatingSecurityContextScheduledExecutorService .
  4. Реализуйте метод configureTasks () интерфейса SchedulingConfigurer . Этот метод принимает объект ScheduledTaskRegistrar в качестве параметра метода. Реализуйте этот метод, выполнив следующие действия:
    1. Создайте новый объект Executor , вызвав метод taskExecutor () .
    2. Установите используемый планировщик, вызвав метод setScheduler () класса ScheduledTaskRegistrar и передав объект Executor в качестве параметра метода.

Исходный код класса ExampleApplicationContext выглядит следующим образом (соответствующие части выделены):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
 
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
 
@Configuration
@EnableScheduling
@ComponentScan(basePackages = {
        "net.petrikainulainen.spring.trenches.scheduling"
})
@Import(ExampleSecurityContext.class)
@PropertySource("classpath:application.properties")
public class ExampleApplicationContext implements SchedulingConfigurer {
 
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }
 
    @Bean
    public Executor taskExecutor() {
        ScheduledExecutorService delegateExecutor = Executors.newSingleThreadScheduledExecutor();
        SecurityContext schedulerContext = createSchedulerSecurityContext();
        return new DelegatingSecurityContextScheduledExecutorService(delegateExecutor, schedulerContext);
    }
 
    private SecurityContext createSchedulerSecurityContext() {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
 
        Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                "user",
                "ROLE_USER",
                authorities
        );
        context.setAuthentication(authentication);
 
        return context;
    }
 
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
 
        properties.setLocation(new ClassPathResource( "application.properties" ));
        properties.setIgnoreResourceNotFound(false);
 
        return properties;
    }
}

Вот и все. Эта конфигурация гарантирует, что каждое запланированное задание имеет доступ к объекту SecurityContext, созданному методом createSchedulerSecurityContext () . Это означает, что каждое запланированное задание может вызывать защищенные методы, которые могут быть вызваны пользователем с ролью «ROLE_USER».

Давайте кратко рассмотрим нашу запланированную работу.

Как насчет запланированной работы?

Лучшая часть этого решения заключается в том, что нам не нужно вносить какие-либо изменения в класс ScheduledJob . Его исходный код выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
@Component
public class ScheduledJob {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);
 
    private final MessageService messageService;
 
    @Autowired
    public ScheduledJob(MessageService messageService) {
        this.messageService = messageService;
    }
 
    @Scheduled(cron = "${scheduling.job.cron}")
    public void run() {
        String message = messageService.getMessage();
        LOGGER.debug("Received message: {}", message);
    }
}

Когда запускается запланированное задание, в журнал записывается следующая строка:

1
2013-12-17 21:12:14,012 DEBUG - ScheduledJob            - Received message: Hello World!

Довольно круто. Правильно?

Резюме

Теперь мы успешно создали запланированные задания, которые могут вызывать защищенный метод. Этот урок научил нас трем вещам:

  • Мы узнали, что обычно объект SecurityContext хранится в ThreadLocal, что означает, что все запланированные задания, выполняемые в одном потоке, используют один и тот же контекст безопасности
  • Мы узнали, что если наше приложение использует Spring Security 3.1 и мы хотим вызывать защищенный метод из запланированного задания, самый простой способ сделать это — настроить используемый объект аутентификации в каждом запланированном задании.
  • Мы узнали, как мы можем использовать поддержку параллелизма Spring Security 3.2 и передавать объект SecurityContext из одного потока в другой.

Вы можете получить примеры приложений этого поста в блоге от Github ( Spring Security 3.1 и Spring Security 3.2 ).

Примечание. Конфигурация XML примера Spring Security 3.2 в настоящий момент не работает. Я исправлю это, когда у меня будет время, чтобы сделать это.