Рубрики
Без рубрики

Распространение контекста безопасности Spring с помощью @Async

Краткий пример распространения контекста безопасности Spring при использовании аннотации @Async

Автор оригинала: baeldung.

1. введение

В этом уроке мы сосредоточимся на распространении принципа безопасности Spring с помощью @Async .

По умолчанию проверка подлинности Spring Security привязана к ThreadLocal – таким образом, когда поток выполнения выполняется в новом потоке с @Async, это не будет аутентифицированным контекстом.

Это не идеально – давайте исправим это.

2. Зависимости Maven

Чтобы использовать асинхронную интеграцию в Spring Security, нам необходимо включить следующий раздел в зависимости нашего pom.xml :


    org.springframework.security
    spring-security-config
    5.2.3.RELEASE

Последнюю версию зависимостей безопасности Spring можно найти здесь .

3. Весеннее Распространение Безопасности С Помощью @Async

Давайте сначала напишем простой пример:

@RequestMapping(method = RequestMethod.GET, value = "/async")
@ResponseBody
public Object standardProcessing() throws Exception {
    log.info("Outside the @Async logic - before the async call: "
      + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
    
    asyncService.asyncCall();
    
    log.info("Inside the @Async logic - after the async call: "
      + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
    
    return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}

Мы хотим проверить, распространяется ли контекст Spring Security на новый поток. Сначала мы регистрируем контекст перед асинхронным вызовом, затем запускаем асинхронный метод и, наконец, снова регистрируем контекст. Метод asyncCall() имеет следующую реализацию:

@Async
@Override
public void asyncCall() {
    log.info("Inside the @Async logic: "
      + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}

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

4. Конфигурация По Умолчанию

По умолчанию контекст безопасности внутри @Async метод будет иметь нулевой ценность.

В частности, если мы запустим асинхронную логику, мы сможем зарегистрировать объект Authentication в основной программе, но когда мы зарегистрируем его внутри @Async , он будет null . Это пример вывода журналов:

web - 2016-12-30 22:41:58,916 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Outside the @Async logic - before the async call:
  [email protected]:
  Username: temporary; ...

web - 2016-12-30 22:41:58,921 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Inside the @Async logic - after the async call:
  [email protected]:
  Username: temporary; ...

  web - 2016-12-30 22:41:58,926 [SimpleAsyncTaskExecutor-1] ERROR
  o.s.a.i.SimpleAsyncUncaughtExceptionHandler -
  Unexpected error occurred invoking async method
  'public void com.baeldung.web.service.AsyncServiceImpl.asyncCall()'.
  java.lang.NullPointerException: null

Итак, как вы можете видеть, внутри потока исполнителя наш вызов завершается неудачей с NPE, как и ожидалось, потому что Принципал там недоступен.

5. Настройка контекста Асинхронной Безопасности

Если мы хотим иметь доступ к принципалу внутри асинхронного потока, так же как у нас есть доступ к нему снаружи, нам нужно будет создать DelegatingSecurityContextAsyncTaskExecutor bean:

@Bean 
public DelegatingSecurityContextAsyncTaskExecutor taskExecutor(ThreadPoolTaskExecutor delegate) { 
    return new DelegatingSecurityContextAsyncTaskExecutor(delegate); 
}

Таким образом, Spring будет использовать текущий Контекст безопасности внутри каждого @Async вызова.

Теперь давайте снова запустим приложение и посмотрим на информацию журнала, чтобы убедиться, что это так:

web - 2016-12-30 22:45:18,013 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Outside the @Async logic - before the async call:
  [email protected]:
  Username: temporary; ...

web - 2016-12-30 22:45:18,018 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Inside the @Async logic - after the async call:
  [email protected]:
  Username: temporary; ...

web - 2016-12-30 22:45:18,019 [SimpleAsyncTaskExecutor-1] INFO
  o.baeldung.web.service.AsyncService -
  Inside the @Async logic:
  [email protected]:
  Username: temporary; ...

И вот мы здесь – как мы и ожидали, мы видим одного и того же участника в потоке асинхронного исполнителя.

6. Примеры использования

Есть несколько интересных случаев использования, в которых мы могли бы убедиться, что контекст безопасности распространяется следующим образом:

  • мы хотим сделать несколько внешних запросов, которые могут выполняться параллельно и выполнение которых может занять значительное время
  • нам нужно выполнить некоторую значительную обработку локально, и наш внешний запрос может выполняться параллельно с этим
  • другие представляют собой сценарии “огонь и забудь”, например, отправку электронной почты

7. Заключение

В этом кратком руководстве мы представили поддержку Spring для отправки асинхронных запросов с распространяемым SecurityContext. С точки зрения модели программирования новые возможности кажутся обманчиво простыми.

Обратите внимание, что если несколько вызовов методов ранее были связаны синхронно, преобразование в асинхронный подход может потребовать синхронизации результатов.

Этот пример также доступен в качестве проекта Maven на Github .