123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- [[concurrency]]
- = Concurrency Support
- In most environments, Security is stored on a per `Thread` basis.
- This means that when work is done on a new `Thread`, the `SecurityContext` is lost.
- Spring Security provides some infrastructure to help make this much easier for users.
- Spring Security provides low level abstractions for working with Spring Security in multi-threaded environments.
- 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].
- == DelegatingSecurityContextRunnable
- One of the most fundamental building blocks within Spring Security's concurrency support is the `DelegatingSecurityContextRunnable`.
- It wraps a delegate `Runnable` in order to initialize the `SecurityContextHolder` with a specified `SecurityContext` for the delegate.
- It then invokes the delegate Runnable ensuring to clear the `SecurityContextHolder` afterwards.
- The `DelegatingSecurityContextRunnable` looks something like this:
- ====
- .Java
- [source,java,role="primary"]
- ----
- public void run() {
- try {
- SecurityContextHolder.setContext(securityContext);
- delegate.run();
- } finally {
- SecurityContextHolder.clearContext();
- }
- }
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- fun run() {
- try {
- SecurityContextHolder.setContext(securityContext)
- delegate.run()
- } finally {
- SecurityContextHolder.clearContext()
- }
- }
- ----
- ====
- While very simple, it makes it seamless to transfer the SecurityContext from one Thread to another.
- This is important since, in most cases, the SecurityContextHolder acts on a per Thread basis.
- 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.
- You can now easily transfer the `SecurityContext` of the current `Thread` to the `Thread` that invokes the secured service.
- An example of how you might do this can be found below:
- ====
- .Java
- [source,java,role="primary"]
- ----
- Runnable originalRunnable = new Runnable() {
- public void run() {
- // invoke secured service
- }
- };
- SecurityContext context = SecurityContextHolder.getContext();
- DelegatingSecurityContextRunnable wrappedRunnable =
- new DelegatingSecurityContextRunnable(originalRunnable, context);
- new Thread(wrappedRunnable).start();
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- val originalRunnable = Runnable {
- // invoke secured service
- }
- val context: SecurityContext = SecurityContextHolder.getContext()
- val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable, context)
- Thread(wrappedRunnable).start()
- ----
- ====
- The code above performs the following steps:
- * Creates a `Runnable` that will be invoking our secured service.
- Notice that it is not aware of Spring Security
- * Obtains the `SecurityContext` that we wish to use from the `SecurityContextHolder` and initializes the `DelegatingSecurityContextRunnable`
- * Use the `DelegatingSecurityContextRunnable` to create a Thread
- * Start the Thread we created
- Since it is quite common to create a `DelegatingSecurityContextRunnable` with the `SecurityContext` from the `SecurityContextHolder` there is a shortcut constructor for it.
- The following code is the same as the code above:
- ====
- .Java
- [source,java,role="primary"]
- ----
- Runnable originalRunnable = new Runnable() {
- public void run() {
- // invoke secured service
- }
- };
- DelegatingSecurityContextRunnable wrappedRunnable =
- new DelegatingSecurityContextRunnable(originalRunnable);
- new Thread(wrappedRunnable).start();
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- val originalRunnable = Runnable {
- // invoke secured service
- }
- val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable)
- Thread(wrappedRunnable).start()
- ----
- ====
- The code we have is simple to use, but it still requires knowledge that we are using Spring Security.
- 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.
- == DelegatingSecurityContextExecutor
- 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.
- Let's take a look at how `DelegatingSecurityContextExecutor` can shield our code from any knowledge that we are using Spring Security.
- The design of `DelegatingSecurityContextExecutor` is very similar to that of `DelegatingSecurityContextRunnable` except it accepts a delegate `Executor` instead of a delegate `Runnable`.
- You can see an example of how it might be used below:
- ====
- .Java
- [source,java,role="primary"]
- ----
- SecurityContext context = SecurityContextHolder.createEmptyContext();
- Authentication authentication =
- UsernamePasswordAuthenticationToken.authenticated("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"));
- context.setAuthentication(authentication);
- SimpleAsyncTaskExecutor delegateExecutor =
- new SimpleAsyncTaskExecutor();
- DelegatingSecurityContextExecutor executor =
- new DelegatingSecurityContextExecutor(delegateExecutor, context);
- Runnable originalRunnable = new Runnable() {
- public void run() {
- // invoke secured service
- }
- };
- executor.execute(originalRunnable);
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- val context: SecurityContext = SecurityContextHolder.createEmptyContext()
- val authentication: Authentication =
- UsernamePasswordAuthenticationToken("user", "doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"))
- context.authentication = authentication
- val delegateExecutor = SimpleAsyncTaskExecutor()
- val executor = DelegatingSecurityContextExecutor(delegateExecutor, context)
- val originalRunnable = Runnable {
- // invoke secured service
- }
- executor.execute(originalRunnable)
- ----
- ====
- The code performs the following steps:
- * Creates the `SecurityContext` to be used for our `DelegatingSecurityContextExecutor`.
- Note that in this example we simply create the `SecurityContext` by hand.
- However, it does not matter where or how we get the `SecurityContext` (i.e. we could obtain it from the `SecurityContextHolder` if we wanted).
- * Creates a delegateExecutor that is in charge of executing submitted ``Runnable``s
- * Finally we create a `DelegatingSecurityContextExecutor` which is in charge of wrapping any Runnable that is passed into the execute method with a `DelegatingSecurityContextRunnable`.
- It then passes the wrapped Runnable to the delegateExecutor.
- In this instance, the same `SecurityContext` will be used for every Runnable submitted to our `DelegatingSecurityContextExecutor`.
- This is nice if we are running background tasks that need to be run by a user with elevated privileges.
- * 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`.
- ====
- .Java
- [source,java,role="primary"]
- ----
- @Autowired
- private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor
- public void submitRunnable() {
- Runnable originalRunnable = new Runnable() {
- public void run() {
- // invoke secured service
- }
- };
- executor.execute(originalRunnable);
- }
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- @Autowired
- lateinit var executor: Executor // becomes an instance of our DelegatingSecurityContextExecutor
- fun submitRunnable() {
- val originalRunnable = Runnable {
- // invoke secured service
- }
- executor.execute(originalRunnable)
- }
- ----
- ====
- 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.
- In this example, the same user is being used to run each thread.
- 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``?
- This can be done by removing the `SecurityContext` argument from our `DelegatingSecurityContextExecutor` constructor.
- For example:
- ====
- .Java
- [source,java,role="primary"]
- ----
- SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
- DelegatingSecurityContextExecutor executor =
- new DelegatingSecurityContextExecutor(delegateExecutor);
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- val delegateExecutor = SimpleAsyncTaskExecutor()
- val executor = DelegatingSecurityContextExecutor(delegateExecutor)
- ----
- ====
- 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`.
- This means that we are running our `Runnable` with the same user that was used to invoke the `executor.execute(Runnable)` code.
- == Spring Security Concurrency Classes
- Refer to the Javadoc for additional integrations with both the Java concurrent APIs and the Spring Task abstractions.
- They are quite self-explanatory once you understand the previous code.
- * `DelegatingSecurityContextCallable`
- * `DelegatingSecurityContextExecutor`
- * `DelegatingSecurityContextExecutorService`
- * `DelegatingSecurityContextRunnable`
- * `DelegatingSecurityContextScheduledExecutorService`
- * `DelegatingSecurityContextSchedulingTaskExecutor`
- * `DelegatingSecurityContextAsyncTaskExecutor`
- * `DelegatingSecurityContextTaskExecutor`
- * `DelegatingSecurityContextTaskScheduler`
|