Spring from the Trenches: Invoking a Secured Method from a Scheduled Job
Let’s assume that we have implemented a Spring powered application, and secured it by using the method security expressions of Spring Security.
Our next task is to implement a scheduled job which uses the secured methods. To be more specific, we have to implement a scheduled job which obtains a message from our service class and writes the received message to the log.
Let’s get started.
The scheduled jobs described in this blog post use cron expressions which are configured in the profile specific configuration files. If you don’t know how you can do this, I recommend that you read my blog post which describes how you can use environment specific cron expressions with the @Scheduled annotation.
Our First Attempt
Let’s create a scheduled job which invokes the secured method and find out what happens when the job is executed. Let’s start by taking a look at the service layer of our example application.
The Service Layer
The methods of the secured service class are declared in the MessageService interface. It declares one method called getMessage() and specifies that only users who have a role ROLE_USER can invoke it.
The source code of the MessageService interface looks as follows:
import org.springframework.security.access.prepost.PreAuthorize; public interface MessageService { @PreAuthorize("hasRole('ROLE_USER')") public String getMessage(); }
Our implementation of the MessageService interface is rather simple. Its source code looks as follows:
import org.springframework.stereotype.Service; @Service public class HelloMessageService implements MessageService { @Override public String getMessage() { return "Hello World!"; } }
Let’s move on and create scheduled job which invokes the getMessage() method.
Creating the Scheduled Job
We can create the scheduled job by following these steps:
- Create a ScheduledJob class and annotate it with the @Component annotation. This ensures that our scheduled job is found during the classpath scan (as long as we put it to a package which is scanned).
- Add a private Logger field to the created class and create a Logger object by calling the static getLogger() method of the LoggerFactory class. We will use the Logger object to write the message which we receive from the HelloMessageService object to the log.
- Add a private MessageService field to created class.
- Add a constructor to the created class and annotate it with the @Autowired annotation. This ensures that we can inject a MessageService bean to the MessageService field by using constructor injection.
- Add a public run() method to created class and annotate it with the @Scheduled annotation. Set the value of its cron attribute to ‘${scheduling.job.cron}’. That means that the cron expression is read from a properties file, and its value is the value of the scheduling.job.cron property (See this blog post for more details about this).
- Implement the run() method by calling the getMessage() method of the MessageService interface. Write the received message to the log.
The source code of our scheduled job looks as follows:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class ScheduledJob { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class); private final MessageService messageService; @Autowired public ScheduledJob(MessageService messageService) { this.messageService = messageService; } @Scheduled(cron = "${scheduling.job.cron}") public void run() { String message = messageService.getMessage(); LOGGER.debug("Received message: {}", message); } }
Let’s see what happens when the run() method of the ScheduledJob class is invoked.
It Doesn’t Work
When our scheduled job is executed, the AuthenticationCredentialsNotFoundException is thrown and we see the following stacktrace:
2013-12-10 19:45:19,001 ERROR - kUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task. org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:339) at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:198) at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204) at com.sun.proxy.$Proxy31.getMessage(Unknown Source) at net.petrikainulainen.spring.trenches.scheduling.job.ScheduledJobTwo.run(ScheduledJobTwo.java:26) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:64) at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:53) at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334) at java.util.concurrent.FutureTask.run(FutureTask.java:166) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:722)
That stacktrace is actually pretty helpful. It tells us that the secured method couldn’t be invoked because an Authentication object was not found from the SecurityContext.
The two most common solutions to this problem which I have seen are:
- Create a separate method which does the same thing than the protected method, and modify the scheduled job to use this method. This method often has a Javadoc comment which states that the only the scheduled job can call this method. This solution has two problems: 1) it clutters the codebase and 2) someone will eventually call that method anyway (no one really read Javadocs unless they have to).
- Remove the method security annotation from the method invoked by the scheduled job. This is a really poor solution for obvious reasons. Hint: That method was secured for a good reason!
Luckily, there is also a third way to solve this problem. Let’s start by finding out where the security context used by our scheduled job is stored.
Where Does the Security Context Come From?
The solution of our problem is clear: We have to create an Authentication object and add it to the SecurityContext before the secured method is invoked.
However, before we can make the necessary modifications to our example application, we have to understand where the SecurityContext object is stored.
If we haven’t configured otherwise, the security context is stored to ThreadLocal. In other words, each thread has its own security context. This means that all scheduled jobs which are executed in the same thread share the same security context.
Let’s assume that we have three scheduled jobs. These jobs are called A, B, and C. Also, let’s assume that these jobs are executed in alphabetical order.
If we use the default thread pool which has only one thread, all jobs share the same security context. If the job B sets the Authentication object to the security context, the following things happen when the scheduled jobs are executed:
- The job A cannot invoke the secured method because it is executed before job B. This means that an Authentication object is not found from the security context.
- The job B can invoke the secured method because it sets the Authentication object to the security context before it tries to invoke the secured method.
- The job C can invoke the secured method because it is executed after job B which sets the Authentication object to the security context.
If we use a thread pool which has more than one thread, each thread has its own security context. If the job A sets the Authentication object to the security context, all jobs which are executed in the same thread are executed by using the same privileges as long as they are executed after job A.
Let’s go through each job one by one:
- The job A can invoke the secured method because it sets the Authentication object to the security context before it tries to invoke the secured method.
- The job B can invoke the secured method IF it is executed in the same thread than job A. If the job is isn’t executed in the same thread, it cannot invoke the secured method because the Authentication object is not found from the security context.
- The job C can invoke the secured method IF it is executed in the same thread than job A. If the job is isn’t executed in the same thread, it cannot invoke the secured method because the Authentication object is not found from the security context.
It is clear that the best way to solve this problem is to ensure that each scheduled job is executed by using the required privileges. This solution has two benefits:
- We can execute our jobs in any order.
- We don’t have to ensure that jobs are executed in a “correct” thread.
Let’s find out how we can solve this problem when our application uses Spring Security 3.1.
Spring Security 3.1: Manual Work Required
If our application uses Spring Security 3.1, the easiest way to solve our problem is
- Create an Authentication object and set it to the security context before our job tries to invoke the secured method.
- Remove the Authentication object from the security context before the job is finished.
Let’s start by creating an AuthenticationUtil class which provides the required methods.
Creating the AuthenticationUtil Class
We can create the AuthenticationUtil class by following these steps:
- Create the AuthenticationUtil class.
- Add a private constructor the AuthenticationUtil class. This ensures that the class cannot be instantiated.
- Add a static clearAuthentication() method to the class, and implement the method by following these steps:
- Get the SecurityContext object by calling the static getContext() method of the SecurityContextHolder class.
- Remove the authentication information by calling the setContext() method of the SecurityContext interface. Pass null as a method parameter.
- Add a static configureAuthentication() method to the class. This method takes the role of the user as a method parameter. Implement this method by following these steps:
- Create a Collection of GrantedAuthority objects by calling the static createAuthorityList() method of the AuthorityUtils class. Pass the user role as a method parameter.
- Create a new UsernamePasswordAuthenticationToken object and pass the following objects as constructor arguments:
- The first constructor argument is the principal. Pass the String ‘user’ as the first constructor argument.
- The second constructor argument is the credentials of the user. Pass the role given as a method parameter as the second constructor argument.
- The third constructor argument contains the authorities of the user. Pass the created Collection<GrantedAuthority> object as the third constructor argument.
- Get the SecurityContext object by calling the static getContext() method of the SecurityContextHolder class.
- Set the created Authentication object to the security context by calling the setAuthentication() method of the SecurityContext interface. Pass the created UsernamePasswordAuthenticationToken as a method parameter.
The source code of the AuthenticationUtil class looks as follows:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import java.util.Collection; public final class AuthenticationUtil { //Ensures that this class cannot be instantiated private AuthenticationUtil() { } public static void clearAuthentication() { SecurityContextHolder.getContext().setAuthentication(null); } public static void configureAuthentication(String role) { Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(role); Authentication authentication = new UsernamePasswordAuthenticationToken( "user", role, authorities ); SecurityContextHolder.getContext().setAuthentication(authentication); } }
We are not done yet. We still have to make some modifications to our scheduled job. Let’s find out how we can make these modifications.
Modifying the Scheduled Job
We have to make two modifications to the ScheduledJob class. We can make these modifications by following these steps:
- Call the static configureAuthentication() method of the AuthenticationUtil class when the job is started and pass the String ‘ROLE_USER’ as a method parameter. This ensures that our scheduled job can execute the same methods than a regular user who has a role ROLE_USER.
- Call the static clearAuthentication() method of the AuthenticationUtil class just before the job is finished. This removed the authentication information from the security context.
The source code of the ScheduledJob class looks as follows:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class ScheduledJob { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class); private final MessageService messageService; @Autowired public ScheduledJob(MessageService messageService) { this.messageService = messageService; } @Scheduled(cron = "${scheduling.job.cron}") public void run() { AuthenticationUtil.configureAuthentication("ROLE_USER"); String message = messageService.getMessage(); LOGGER.debug("Received message: {}", message); AuthenticationUtil.clearAuthentication(); } }
Let’s find out what happens when our scheduled job is run.
Running the Scheduled Job
When the job is invoked, the following message is written to the log:
2013-12-17 20:41:33,019 DEBUG - ScheduledJob - Received message: Hello World!
Everything is working perfectly when our application uses Spring Security 3.1. Our solution is not that elegant, but it works. The obvious drawback of this solution is that we have to remember to call the configureAuthentication() and clearAuthentication() methods of the AuthenticationUtil class in our scheduled jobs.
Spring Security 3.2 solves this problem. Let’s move on and find out how we can solve this problem when our application uses Spring Security 3.2.
Spring Security 3.2: It Is Almost Like Magic!
Spring Security 3.2 has a brand new concurrency support which gives us the possibility to transfer the security context from one thread to another. Let’s find out how we can configure our application context to use the features provided by Spring Security 3.2.
Configuring the Application Context
Because we want to use the new concurrency support of Spring Security 3.2, we have to make the following changes to our application context configuration class (the original configuration is described in this blog post):
- Implement the SchedulingConfigurer interface. This interface can be implemented by application context configuration classes which are annotated with the @EnableScheduling annotation, and it is often used to to configure the used TaskScheduler bean or configure the executed tasks programmatically.
- Add a private createrSchedulerSecurityContext() method to the configuration class. This method has no method parameters it returns a SecurityContext object. Implement this method by following these steps:
- Create a new SecurityContext object by calling the static createEmptyContext() method of the SecurityContextHolder class.
- Create a Collection of GrantedAuthority objects by calling the static createAuthorityList() method of the AuthorityUtils class. Pass the String ‘ROLE_USER’ as a method parameter.
- Create a new UsernamePasswordAuthenticationToken object and pass the following objects as constructor arguments:
- The first constructor argument is the principal. Pass the String ‘user’ as the first constructor argument.
- The second constructor argument is the credentials of the user. Pass the String ‘ROLE_USER’ as the second constructor argument.
- The third constructor argument contains the authorities of the user. Pass the created Collection<GrantedAuthority> object as the third constructor argument.
- Set the created UsernamePasswordAuthenticationToken object to the created security context by calling the setAuthentication() method of the SecurityContext interface.
- Add a public taskExecutor() method to the configuration class and annotate the method with the @Bean annotation. This method has no method parameters and returns an Executor object. Implement this method by following these steps:
- Create a new ScheduledExecutorService object by calling the static newSingleThreadScheduledExecutor() method of the Executors class. This creates a ScheduledExecutorService object which runs all jobs by using a single thread.
- Obtain a reference to the SecurityContext object by calling the private createSchedulerSecurityContext() method.
- Create a new DelegatingSecurityContextScheduledExecutorService object and pass the following objects as constructor arguments:
- The first constructor argument is a ScheduledExecutorService object. This object is used to invoke scheduled jobs. Pass the created ScheduledExecutorService object as the first constructor argument.
- The second constructor argument is a SecurityContext object. The created DelegatingSecurityContextScheduledExecutorService object ensures that each invoked job uses this SecurityContext. Pass the created the SecurityContext object as the second constructor argument.
- Return the created DelegatingSecurityContextScheduledExecutorService object.
- Implement the configureTasks() method of the of the SchedulingConfigurer interface. This method takes a ScheduledTaskRegistrar object as a method parameter. Implement this method by following these steps:
- Create a new Executor object by calling the taskExecutor() method.
- Set the used scheduler by calling the setScheduler() method of the ScheduledTaskRegistrar class and pass the Executor object as a method parameter.
The source code of the ExampleApplicationContext class looks as follows (the relevant parts are highlighted):
import org.springframework.context.annotation.*; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import java.util.Collection; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @Configuration @EnableScheduling @ComponentScan(basePackages = { "net.petrikainulainen.spring.trenches.scheduling" }) @Import(ExampleSecurityContext.class) @PropertySource("classpath:application.properties") public class ExampleApplicationContext implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskExecutor()); } @Bean public Executor taskExecutor() { ScheduledExecutorService delegateExecutor = Executors.newSingleThreadScheduledExecutor(); SecurityContext schedulerContext = createSchedulerSecurityContext(); return new DelegatingSecurityContextScheduledExecutorService(delegateExecutor, schedulerContext); } private SecurityContext createSchedulerSecurityContext() { SecurityContext context = SecurityContextHolder.createEmptyContext(); Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER"); Authentication authentication = new UsernamePasswordAuthenticationToken( "user", "ROLE_USER", authorities ); context.setAuthentication(authentication); return context; } @Bean public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer(); properties.setLocation(new ClassPathResource( "application.properties" )); properties.setIgnoreResourceNotFound(false); return properties; } }
That is it. This configuration ensures that each scheduled job has access to the SecurityContext object created by the createSchedulerSecurityContext() method. This means that each scheduled job can invoke secured methods which can be invoked by a user who has the role ‘ROLE_USER’.
Let’s take a quick look at our scheduled job.
What About the Scheduled Job?
The best part of this solution is that we don’t have to make any changes to the ScheduledJob class. Its source code looks as follows:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class ScheduledJob { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class); private final MessageService messageService; @Autowired public ScheduledJob(MessageService messageService) { this.messageService = messageService; } @Scheduled(cron = "${scheduling.job.cron}") public void run() { String message = messageService.getMessage(); LOGGER.debug("Received message: {}", message); } }
When the scheduled job is invoked, the following line is written to the log:
2013-12-17 21:12:14,012 DEBUG - ScheduledJob - Received message: Hello World!
Pretty cool. Right?
Summary
We have now successfully created scheduled jobs which can invoke secured method. This tutorial has taught us three things:
- We learned that typically the SecurityContext object is stored to ThreadLocal which means that all scheduled jobs executed in the same thread share the same security context
- We learned that if our application uses Spring Security 3.1, and we want to invoke a secured method from a scheduled job, the easiest way to do this is configure the used Authentication object in every scheduled job.
- We learned how we can use the concurrency support of Spring Security 3.2 and transfer the SecurityContext object from one thread to another.
You can get the example applications of this blog post from Github (Spring Security 3.1 and Spring Security 3.2).
Note: The XML configuration of the Spring Security 3.2 example is not working at the moment. I will fix it when I have got time to do it.