2
0

concurrency.adoc 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. [[concurrency]]
  2. = Concurrency Support
  3. In most environments, Security is stored on a per `Thread` basis.
  4. This means that when work is done on a new `Thread`, the `SecurityContext` is lost.
  5. Spring Security provides some infrastructure to help make this much easier for users.
  6. Spring Security provides low level abstractions for working with Spring Security in multi-threaded environments.
  7. In fact, this is what Spring Security builds on to integration with xref:servlet/integrations/servlet-api.adoc#servletapi-start-runnable[AsyncContext.start(Runnable)] and xref:servlet/integrations/mvc.adoc#mvc-async[Spring MVC Async Integration].
  8. == DelegatingSecurityContextRunnable
  9. One of the most fundamental building blocks within Spring Security's concurrency support is the `DelegatingSecurityContextRunnable`.
  10. It wraps a delegate `Runnable` in order to initialize the `SecurityContextHolder` with a specified `SecurityContext` for the delegate.
  11. It then invokes the delegate Runnable ensuring to clear the `SecurityContextHolder` afterwards.
  12. The `DelegatingSecurityContextRunnable` looks something like this:
  13. [tabs]
  14. ======
  15. Java::
  16. +
  17. [source,java,role="primary"]
  18. ----
  19. public void run() {
  20. try {
  21. SecurityContextHolder.setContext(securityContext);
  22. delegate.run();
  23. } finally {
  24. SecurityContextHolder.clearContext();
  25. }
  26. }
  27. ----
  28. Kotlin::
  29. +
  30. [source,kotlin,role="secondary"]
  31. ----
  32. fun run() {
  33. try {
  34. SecurityContextHolder.setContext(securityContext)
  35. delegate.run()
  36. } finally {
  37. SecurityContextHolder.clearContext()
  38. }
  39. }
  40. ----
  41. ======
  42. While very simple, it makes it seamless to transfer the SecurityContext from one Thread to another.
  43. This is important since, in most cases, the SecurityContextHolder acts on a per Thread basis.
  44. For example, you might have used Spring Security's xref:servlet/appendix/namespace/method-security.adoc#nsa-global-method-security[<global-method-security>] support to secure one of your services.
  45. You can now easily transfer the `SecurityContext` of the current `Thread` to the `Thread` that invokes the secured service.
  46. An example of how you might do this can be found below:
  47. [tabs]
  48. ======
  49. Java::
  50. +
  51. [source,java,role="primary"]
  52. ----
  53. Runnable originalRunnable = new Runnable() {
  54. public void run() {
  55. // invoke secured service
  56. }
  57. };
  58. SecurityContext context = SecurityContextHolder.getContext();
  59. DelegatingSecurityContextRunnable wrappedRunnable =
  60. new DelegatingSecurityContextRunnable(originalRunnable, context);
  61. new Thread(wrappedRunnable).start();
  62. ----
  63. Kotlin::
  64. +
  65. [source,kotlin,role="secondary"]
  66. ----
  67. val originalRunnable = Runnable {
  68. // invoke secured service
  69. }
  70. val context: SecurityContext = SecurityContextHolder.getContext()
  71. val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable, context)
  72. Thread(wrappedRunnable).start()
  73. ----
  74. ======
  75. The code above performs the following steps:
  76. * Creates a `Runnable` that will be invoking our secured service.
  77. Notice that it is not aware of Spring Security
  78. * Obtains the `SecurityContext` that we wish to use from the `SecurityContextHolder` and initializes the `DelegatingSecurityContextRunnable`
  79. * Use the `DelegatingSecurityContextRunnable` to create a Thread
  80. * Start the Thread we created
  81. Since it is quite common to create a `DelegatingSecurityContextRunnable` with the `SecurityContext` from the `SecurityContextHolder` there is a shortcut constructor for it.
  82. The following code is the same as the code above:
  83. [tabs]
  84. ======
  85. Java::
  86. +
  87. [source,java,role="primary"]
  88. ----
  89. Runnable originalRunnable = new Runnable() {
  90. public void run() {
  91. // invoke secured service
  92. }
  93. };
  94. DelegatingSecurityContextRunnable wrappedRunnable =
  95. new DelegatingSecurityContextRunnable(originalRunnable);
  96. new Thread(wrappedRunnable).start();
  97. ----
  98. Kotlin::
  99. +
  100. [source,kotlin,role="secondary"]
  101. ----
  102. val originalRunnable = Runnable {
  103. // invoke secured service
  104. }
  105. val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable)
  106. Thread(wrappedRunnable).start()
  107. ----
  108. ======
  109. The code we have is simple to use, but it still requires knowledge that we are using Spring Security.
  110. In the next section we will take a look at how we can utilize `DelegatingSecurityContextExecutor` to hide the fact that we are using Spring Security.
  111. == DelegatingSecurityContextExecutor
  112. In the previous section we found that it was easy to use the `DelegatingSecurityContextRunnable`, but it was not ideal since we had to be aware of Spring Security in order to use it.
  113. Let's take a look at how `DelegatingSecurityContextExecutor` can shield our code from any knowledge that we are using Spring Security.
  114. The design of `DelegatingSecurityContextExecutor` is very similar to that of `DelegatingSecurityContextRunnable` except it accepts a delegate `Executor` instead of a delegate `Runnable`.
  115. You can see an example of how it might be used below:
  116. [tabs]
  117. ======
  118. Java::
  119. +
  120. [source,java,role="primary"]
  121. ----
  122. SecurityContext context = SecurityContextHolder.createEmptyContext();
  123. Authentication authentication =
  124. UsernamePasswordAuthenticationToken.authenticated("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"));
  125. context.setAuthentication(authentication);
  126. SimpleAsyncTaskExecutor delegateExecutor =
  127. new SimpleAsyncTaskExecutor();
  128. DelegatingSecurityContextExecutor executor =
  129. new DelegatingSecurityContextExecutor(delegateExecutor, context);
  130. Runnable originalRunnable = new Runnable() {
  131. public void run() {
  132. // invoke secured service
  133. }
  134. };
  135. executor.execute(originalRunnable);
  136. ----
  137. Kotlin::
  138. +
  139. [source,kotlin,role="secondary"]
  140. ----
  141. val context: SecurityContext = SecurityContextHolder.createEmptyContext()
  142. val authentication: Authentication =
  143. UsernamePasswordAuthenticationToken("user", "doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"))
  144. context.authentication = authentication
  145. val delegateExecutor = SimpleAsyncTaskExecutor()
  146. val executor = DelegatingSecurityContextExecutor(delegateExecutor, context)
  147. val originalRunnable = Runnable {
  148. // invoke secured service
  149. }
  150. executor.execute(originalRunnable)
  151. ----
  152. ======
  153. The code performs the following steps:
  154. * Creates the `SecurityContext` to be used for our `DelegatingSecurityContextExecutor`.
  155. Note that in this example we simply create the `SecurityContext` by hand.
  156. However, it does not matter where or how we get the `SecurityContext` (i.e. we could obtain it from the `SecurityContextHolder` if we wanted).
  157. * Creates a delegateExecutor that is in charge of executing submitted ``Runnable``s
  158. * Finally we create a `DelegatingSecurityContextExecutor` which is in charge of wrapping any Runnable that is passed into the execute method with a `DelegatingSecurityContextRunnable`.
  159. It then passes the wrapped Runnable to the delegateExecutor.
  160. In this instance, the same `SecurityContext` will be used for every Runnable submitted to our `DelegatingSecurityContextExecutor`.
  161. This is nice if we are running background tasks that need to be run by a user with elevated privileges.
  162. * At this point you may be asking yourself "How does this shield my code of any knowledge of Spring Security?" Instead of creating the `SecurityContext` and the `DelegatingSecurityContextExecutor` in our own code, we can inject an already initialized instance of `DelegatingSecurityContextExecutor`.
  163. [tabs]
  164. ======
  165. Java::
  166. +
  167. [source,java,role="primary"]
  168. ----
  169. @Autowired
  170. private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor
  171. public void submitRunnable() {
  172. Runnable originalRunnable = new Runnable() {
  173. public void run() {
  174. // invoke secured service
  175. }
  176. };
  177. executor.execute(originalRunnable);
  178. }
  179. ----
  180. Kotlin::
  181. +
  182. [source,kotlin,role="secondary"]
  183. ----
  184. @Autowired
  185. lateinit var executor: Executor // becomes an instance of our DelegatingSecurityContextExecutor
  186. fun submitRunnable() {
  187. val originalRunnable = Runnable {
  188. // invoke secured service
  189. }
  190. executor.execute(originalRunnable)
  191. }
  192. ----
  193. ======
  194. Now our code is unaware that the `SecurityContext` is being propagated to the `Thread`, then the `originalRunnable` is run, and then the `SecurityContextHolder` is cleared out.
  195. In this example, the same user is being used to run each thread.
  196. What if we wanted to use the user from `SecurityContextHolder` at the time we invoked `executor.execute(Runnable)` (i.e. the currently logged in user) to process ``originalRunnable``?
  197. This can be done by removing the `SecurityContext` argument from our `DelegatingSecurityContextExecutor` constructor.
  198. For example:
  199. [tabs]
  200. ======
  201. Java::
  202. +
  203. [source,java,role="primary"]
  204. ----
  205. SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
  206. DelegatingSecurityContextExecutor executor =
  207. new DelegatingSecurityContextExecutor(delegateExecutor);
  208. ----
  209. Kotlin::
  210. +
  211. [source,kotlin,role="secondary"]
  212. ----
  213. val delegateExecutor = SimpleAsyncTaskExecutor()
  214. val executor = DelegatingSecurityContextExecutor(delegateExecutor)
  215. ----
  216. ======
  217. Now anytime `executor.execute(Runnable)` is executed the `SecurityContext` is first obtained by the `SecurityContextHolder` and then that `SecurityContext` is used to create our `DelegatingSecurityContextRunnable`.
  218. This means that we are running our `Runnable` with the same user that was used to invoke the `executor.execute(Runnable)` code.
  219. == Spring Security Concurrency Classes
  220. Refer to the Javadoc for additional integrations with both the Java concurrent APIs and the Spring Task abstractions.
  221. They are quite self-explanatory once you understand the previous code.
  222. * `DelegatingSecurityContextCallable`
  223. * `DelegatingSecurityContextExecutor`
  224. * `DelegatingSecurityContextExecutorService`
  225. * `DelegatingSecurityContextRunnable`
  226. * `DelegatingSecurityContextScheduledExecutorService`
  227. * `DelegatingSecurityContextSchedulingTaskExecutor`
  228. * `DelegatingSecurityContextAsyncTaskExecutor`
  229. * `DelegatingSecurityContextTaskExecutor`
  230. * `DelegatingSecurityContextTaskScheduler`