Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreLast week I announced the release of Spring Security 3.2 M1 that contains improved Servlet 3 support. In this post, I will introduce some of the more exciting features found in the 3.2 M1 release. Specifically, we will take a look at the following new Spring Security features:
You might ask "What is concurrency support doing in a release that has a Servlet 3 focused theme?" The reason is that the concurrency support provides a foundation for all the other features found in this release. While the concurrency support is used by the Servlet 3 integration, it can also serve as building blocks to support concurrency and Spring Security in any application. Let's take a look at Spring Security's concurrency support now.
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:
public void 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 <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:
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable, context);
new Thread(wrappedRunnable).start();
The code above performs the following steps:
Runnable
that will be invoking our secured service. Notice that it is not aware of Spring SecuritySecurityContext
that we wish to use from the SecurityContextHolder
and initializes the DelegatingSecurityContextRunnable
DelegatingSecurityContextRunnable
to create a Thread
Thread
we createdSince 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:
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable);
new 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.
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:
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new UsernamePasswordAuthenticationToken("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);
The code performs the following steps:
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).Runnable
sDelegatingSecurityContextExecutor
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
.
@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);
}
Now our code is unaware that the SecurityContext
is being propagated to the Thread
, then the originalRunnable is executed, and then the SecurityContextHolder is cleared out. In this example, the same user is being used to execute 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:
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new 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 executing our Runnable
with the same user that was used to invoke the executor.execute(Runnable)
code.
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.
Spring Security has supported Servlet API integration for quite some time. However, it wasn't until 3.2 M1 that it supported the new methods added in Servlet 3. In this section we will discuss each of the methods that Spring Security integrates with. If you want to see this in action, you can import Spring Security into Spring Tool Suite using the Gradle Plugin and run servletapi sample application.
Spring Security now integrates with HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse). In short, we can use this method to ensure that a user is authenticated. If they are not authenticated, the configured AuthenticationEntryPoint will be used to request the user to authenticate (i.e. redirect to the login page).
Spring Security now integrates with HttpServletRequest.login(String,String). User's can utilize this method to authenticate a username and password with Spring Security. If authentication fails, a ServletException
that wraps the original Spring Security AuthenticationException
will be thrown. This means if you allow the ServletException
to propagate Spring Security's ExceptionTranslationFilter
will handle it for you. Alternatively, you can catch the ServletException
and handle it yourself.
Spring Security now integrates with HttpServletRequest.logout() by invoking the configured LogoutHandler
implementations. Typically this means that the SecurityContextHolder
will be cleared out, the HttpSession
will be invalidated, any "Remember Me" authentication will be cleaned up, etc. However, the configured LogoutHandler
implementations will vary depending on your Spring Security configuration. It is important to note that after HttpServletRequest.logout()
has been invoked, you are still in charge of writing a response out. Typically this would involve a redirect to the welcome page.
The AsynchContext.start(Runnable) method that ensures your credentials will be propagated to the new Thread
. Using Spring Security's newly added concurrency support, Spring Security overrides the AsyncContext.start(Runnable)
to ensure that the current SecurityContext
is used when processing the Runnable
.
Spring Security now supports Servlet 3, Async requests. So how do you use it?
The first step is to ensure you have updated your web.xml to use the 3.0 schema as shown below:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
</web-app>
Next you need to ensure that your springSecurityFilterChain is setup for processing asynchronous requests.
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>
</filter-mapping>
That's it! Now Spring Security will ensure that your SecurityContext
is propagated on asynchronous requests too.
So what has changed? Internal refactoring within Spring Security will ensure that your SecurityContext
is no longer cleared out when a response was committed on another Thread
, resulting in a user who appeared to be logged out. Additionally, you can use the Spring Security concurrency support and Spring Security's AsyncContext.start(Runnable)
integration to assist you in processing Servlet requests.
[callout title=Associating SecurityContext to Callable's]
More technically speaking, Spring Security integrates with WebAsyncManager. The SecurityContext
that is used to process the Callable
is the SecurityContext
that exists on the SecurityContextHolder
at the time startCallableProcessing is invoked.
[/callout]
As Rossen demonstrated in a previous blog post, Spring Web MVC 3.2 has excellent Servlet 3 Async Support. With no additional configuration, Spring Security will automatically setup the SecurityContext
to the Thread
that executes a Callable
returned by your controllers. For example, the following method will automatically have its Callable
executed with the SecurityContext
that was available when the Callable
was created:
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public Object call() throws Exception {
// ...
return "someView";
}
};
}
There is no automatic integration with a DeferredResult
that is returned by controllers. This is because DeferredResult
is processed by the users and thus there is no way of automatically integrating with it. However, you can still use Concurrency Support to provide transparent integration with Spring Security.
I hope this gives you a better understanding of the changes that are available in Spring Security 3.2. M1 and gets you excited for the next milestone. As a member of the community I encourage you to try out the new milestone and report any bugs/enhancements in JIRA. This feedback is a simple, yet very important way to give back to the community!