Authorization checks with Spring & Jobrunr

302 Views Asked by At

I'm creating an app with 4 roles. I don't have permissions stored in my database, just the roles.

At the controller level, I restrict my routes only to certain roles, for example :

@PreAuthorize("hasRole('ADMIN')")

And in my service's methods, I did a more thorough check as needed.

However, the problem I have with having authorization conditions in my services is that it only works if the SecurityContextHolder.getContext() is not null.

And I realized that this could be the case, for example, in one of my jobs with Jobrunr.

So I inject my service into my job and I want to call a method, but it crashes because it can't retrieve the authenticated user.

Is there a way to manually authenticate a user in the job, is this a good way to do it?


Update following @Ken Chan's reply - code not working :

Jobrunr job :

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class InactiveUsersEmail implements JobRequest {
    private Integer userId;

    @Override
    public Class<InactiveUsersEmailHandler> getJobRequestHandler() {
        return InactiveUsersEmailHandler.class;
    }
}

Run it using DelegatingSecurityContextRunnable:

Stream<DelegatingSecurityContextRunnable> jobs = this.userService
    .findAll()
    .map(user -> {
        Runnable task = () -> new InactiveUsersEmail(user.getId());
        return new DelegatingSecurityContextRunnable(task, this.authorizationComponent.getSecurityContext());
    });

// here not working: 
// cannot resolve method 'enqueue(java.util.stream.Stream<org.springframework.security.concurrent.DelegatingSecurityContextRunnable>)'
BackgroundJobRequest.enqueue(jobs);
1

There are 1 best solutions below

7
Ken Chan On

You can wrap the Runnable that is submitted to Jobrunr by DelegatingSecurityContextRunnable

It requires you to define the SecurityContext for running the Runnable and it will take care of setting up and cleaning SecurityContext before and after running the task.

When configuring the SecurityContext , it is better to align with the existing used authentication mechanism to define an Authentication object that has enough permission to run the job.

Below is a getting started example :


//origin runnable task submitted to Jobrunr
Runnable task = () -> System.out.println("doing some task");

//Configure the SecurityContext that has enough permission to execute the task

SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
// Here it does not matter what values username and password are. Just ensure this user has the the Admin GrantedAuthority and his account is enabled
User user = new User("admin", "password", true, true, true, true, grantedAuthorities);
Authentication authentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
securityContext.setAuthentication(authentication);

//wrap the task by DelegatingSecurityContextRunnable
DelegatingSecurityContextRunnable securedRunnable = new DelegatingSecurityContextRunnable(task,securityContext);

//Submit the secured task to Jobrunr
BackgroundJob.enqueue(()->securedRunnable.run());