|
| 1 | +[[concurrency]] |
| 2 | += Concurrency Support |
| 3 | + |
| 4 | +In most environments, Security is stored on a per `Thread` basis. |
| 5 | +This means that when work is done on a new `Thread`, the `SecurityContext` is lost. |
| 6 | +Spring Security provides some infrastructure to help make this much easier for users. |
| 7 | +Spring Security provides low level abstractions for working with Spring Security in multi-threaded environments. |
| 8 | +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]. |
| 9 | + |
| 10 | +== DelegatingSecurityContextRunnable |
| 11 | + |
| 12 | +One of the most fundamental building blocks within Spring Security's concurrency support is the `DelegatingSecurityContextRunnable`. |
| 13 | +It wraps a delegate `Runnable` in order to initialize the `SecurityContextHolder` with a specified `SecurityContext` for the delegate. |
| 14 | +It then invokes the delegate Runnable ensuring to clear the `SecurityContextHolder` afterwards. |
| 15 | +The `DelegatingSecurityContextRunnable` looks something like this: |
| 16 | + |
| 17 | +[source,java] |
| 18 | +---- |
| 19 | +public void run() { |
| 20 | +try { |
| 21 | + SecurityContextHolder.setContext(securityContext); |
| 22 | + delegate.run(); |
| 23 | +} finally { |
| 24 | + SecurityContextHolder.clearContext(); |
| 25 | +} |
| 26 | +} |
| 27 | +---- |
| 28 | + |
| 29 | +While very simple, it makes it seamless to transfer the SecurityContext from one Thread to another. |
| 30 | +This is important since, in most cases, the SecurityContextHolder acts on a per Thread basis. |
| 31 | +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. |
| 32 | +You can now easily transfer the `SecurityContext` of the current `Thread` to the `Thread` that invokes the secured service. |
| 33 | +An example of how you might do this can be found below: |
| 34 | + |
| 35 | +[source,java] |
| 36 | +---- |
| 37 | +Runnable originalRunnable = new Runnable() { |
| 38 | +public void run() { |
| 39 | + // invoke secured service |
| 40 | +} |
| 41 | +}; |
| 42 | +
|
| 43 | +SecurityContext context = SecurityContextHolder.getContext(); |
| 44 | +DelegatingSecurityContextRunnable wrappedRunnable = |
| 45 | + new DelegatingSecurityContextRunnable(originalRunnable, context); |
| 46 | +
|
| 47 | +new Thread(wrappedRunnable).start(); |
| 48 | +---- |
| 49 | + |
| 50 | +The code above performs the following steps: |
| 51 | + |
| 52 | +* Creates a `Runnable` that will be invoking our secured service. |
| 53 | +Notice that it is not aware of Spring Security |
| 54 | +* Obtains the `SecurityContext` that we wish to use from the `SecurityContextHolder` and initializes the `DelegatingSecurityContextRunnable` |
| 55 | +* Use the `DelegatingSecurityContextRunnable` to create a Thread |
| 56 | +* Start the Thread we created |
| 57 | + |
| 58 | +Since it is quite common to create a `DelegatingSecurityContextRunnable` with the `SecurityContext` from the `SecurityContextHolder` there is a shortcut constructor for it. |
| 59 | +The following code is the same as the code above: |
| 60 | + |
| 61 | + |
| 62 | +[source,java] |
| 63 | +---- |
| 64 | +Runnable originalRunnable = new Runnable() { |
| 65 | +public void run() { |
| 66 | + // invoke secured service |
| 67 | +} |
| 68 | +}; |
| 69 | +
|
| 70 | +DelegatingSecurityContextRunnable wrappedRunnable = |
| 71 | + new DelegatingSecurityContextRunnable(originalRunnable); |
| 72 | +
|
| 73 | +new Thread(wrappedRunnable).start(); |
| 74 | +---- |
| 75 | + |
| 76 | +The code we have is simple to use, but it still requires knowledge that we are using Spring Security. |
| 77 | +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. |
| 78 | + |
| 79 | +== DelegatingSecurityContextExecutor |
| 80 | + |
| 81 | +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. |
| 82 | +Let's take a look at how `DelegatingSecurityContextExecutor` can shield our code from any knowledge that we are using Spring Security. |
| 83 | + |
| 84 | +The design of `DelegatingSecurityContextExecutor` is very similar to that of `DelegatingSecurityContextRunnable` except it accepts a delegate `Executor` instead of a delegate `Runnable`. |
| 85 | +You can see an example of how it might be used below: |
| 86 | + |
| 87 | + |
| 88 | +[source,java] |
| 89 | +---- |
| 90 | +SecurityContext context = SecurityContextHolder.createEmptyContext(); |
| 91 | +Authentication authentication = |
| 92 | + new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER")); |
| 93 | +context.setAuthentication(authentication); |
| 94 | +
|
| 95 | +SimpleAsyncTaskExecutor delegateExecutor = |
| 96 | + new SimpleAsyncTaskExecutor(); |
| 97 | +DelegatingSecurityContextExecutor executor = |
| 98 | + new DelegatingSecurityContextExecutor(delegateExecutor, context); |
| 99 | +
|
| 100 | +Runnable originalRunnable = new Runnable() { |
| 101 | +public void run() { |
| 102 | + // invoke secured service |
| 103 | +} |
| 104 | +}; |
| 105 | +
|
| 106 | +executor.execute(originalRunnable); |
| 107 | +---- |
| 108 | + |
| 109 | +The code performs the following steps: |
| 110 | + |
| 111 | +* Creates the `SecurityContext` to be used for our `DelegatingSecurityContextExecutor`. |
| 112 | +Note that in this example we simply create the `SecurityContext` by hand. |
| 113 | +However, it does not matter where or how we get the `SecurityContext` (i.e. we could obtain it from the `SecurityContextHolder` if we wanted). |
| 114 | +* Creates a delegateExecutor that is in charge of executing submitted ``Runnable``s |
| 115 | +* Finally we create a `DelegatingSecurityContextExecutor` which is in charge of wrapping any Runnable that is passed into the execute method with a `DelegatingSecurityContextRunnable`. |
| 116 | +It then passes the wrapped Runnable to the delegateExecutor. |
| 117 | +In this instance, the same `SecurityContext` will be used for every Runnable submitted to our `DelegatingSecurityContextExecutor`. |
| 118 | +This is nice if we are running background tasks that need to be run by a user with elevated privileges. |
| 119 | +* 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`. |
| 120 | + |
| 121 | +[source,java] |
| 122 | +---- |
| 123 | +@Autowired |
| 124 | +private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor |
| 125 | +
|
| 126 | +public void submitRunnable() { |
| 127 | +Runnable originalRunnable = new Runnable() { |
| 128 | + public void run() { |
| 129 | + // invoke secured service |
| 130 | + } |
| 131 | +}; |
| 132 | +executor.execute(originalRunnable); |
| 133 | +} |
| 134 | +---- |
| 135 | + |
| 136 | +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. |
| 137 | +In this example, the same user is being used to run each thread. |
| 138 | +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``? |
| 139 | +This can be done by removing the `SecurityContext` argument from our `DelegatingSecurityContextExecutor` constructor. |
| 140 | +For example: |
| 141 | + |
| 142 | + |
| 143 | +[source,java] |
| 144 | +---- |
| 145 | +SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); |
| 146 | +DelegatingSecurityContextExecutor executor = |
| 147 | + new DelegatingSecurityContextExecutor(delegateExecutor); |
| 148 | +---- |
| 149 | + |
| 150 | +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`. |
| 151 | +This means that we are running our `Runnable` with the same user that was used to invoke the `executor.execute(Runnable)` code. |
| 152 | + |
| 153 | +== Spring Security Concurrency Classes |
| 154 | + |
| 155 | +Refer to the Javadoc for additional integrations with both the Java concurrent APIs and the Spring Task abstractions. |
| 156 | +They are quite self-explanatory once you understand the previous code. |
| 157 | + |
| 158 | +* `DelegatingSecurityContextCallable` |
| 159 | +* `DelegatingSecurityContextExecutor` |
| 160 | +* `DelegatingSecurityContextExecutorService` |
| 161 | +* `DelegatingSecurityContextRunnable` |
| 162 | +* `DelegatingSecurityContextScheduledExecutorService` |
| 163 | +* `DelegatingSecurityContextSchedulingTaskExecutor` |
| 164 | +* `DelegatingSecurityContextAsyncTaskExecutor` |
| 165 | +* `DelegatingSecurityContextTaskExecutor` |
| 166 | +* `DelegatingSecurityContextTaskScheduler` |
0 commit comments