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

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

1. Вступление

В этом уроке мы сосредоточимся наpropagation of the Spring Security principal with @Async. __

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

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

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

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


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

Последнюю версию зависимостей Spring Security можно найти вhere.

3. Распространение безопасности Spring с@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();
}

We want to check if the Spring SecurityContext is propagated to the new thread. Сначала мы регистрируем контекст перед вызовом async, затем запускаем асинхронный метод и, наконец, снова регистрируем контекст. МетодasyncCall() имеет следующую реализацию:

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

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

4. До стратегииSecurityContextHolder

Прежде чем мы настроим стратегиюSecurityContextHolder, контекст внутри метода@Async будет иметь значениеnull.

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

web - 2016-12-30 22:41:58,916 [http-nio-8081-exec-3] INFO
  o.example.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.example.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 org.example.web.service.AsyncServiceImpl.asyncCall()'.
  java.lang.NullPointerException: null

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

Чтобы предотвратить такое поведение, нам нужно включить стратегиюSecurityContextHolder.MODE_INHERITABLETHREADLOCAL:

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

5. После стратегииSecurityContextHolder

Теперь у нас должен быть доступ к принципалу внутри асинхронного потока, так же как у нас есть доступ к нему снаружи.

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

web - 2016-12-30 22:45:18,013 [http-nio-8081-exec-3] INFO
  o.example.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.example.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.example.web.service.AsyncService -
  Inside the @Async logic:
  [email protected]:
  Username: temporary; ...

И вот мы - как мы и ожидали, мы видим того же принципала внутри потока асинхронного исполнителя. **

6. Случаи применения

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

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

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

  • другие представляют сценарии «забей и забудь», например, отправив электронное письмо

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

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

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

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