Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SecurityContext with default System authentication/user

In my spring application, I would like that a SecurityContext always holds an Authentication. If it's not a regular UsernamePasswordAuthenticationToken, it will be a PreAuthenticatedAuthenticationToken describing the "system user." This has reasons within different system function which requires a user. To avoid a special treatment if there is no user context, I merely want to add the system context. IMHO, this has also to do with the single responsibility principle.

To achieve this, I can simply implement my own SecurityContextHolderStrategy and set the it to the SecurityContextHolder with SecurityContextHolder.setStrategyName(MyStrategyClassName);

Now to the problem:

The default SecurityContextHolderStrategy is the ThreadLocalSecurityContextHolderStrategy. I'm happy with this strategy and how it works. The only thing which I would change is the getContext() method.

public SecurityContext getContext() {
    SecurityContext ctx = CONTEXT_HOLDER.get();

    if (ctx == null) {
        ctx = createEmptyContext();
        CONTEXT_HOLDER.set(ctx);
    }
    return ctx;
}

to

public SecurityContext getContext() {
    SecurityContext ctx = CONTEXT_HOLDER.get();

    if (ctx == null) {
        ctx = createEmptyContext();
        Authentication authentication = new PreAuthenticatedAuthenticationToken("system", null);
        authentication.setAuthenticated(true);
        ctx.setAuthentication(authentication);
        CONTEXT_HOLDER.set(ctx);
    }
    return ctx;
}

This is not possible as the ThreadLocalSecurityContextHolderStrategy class is not public. Of course I can simply copy paste the code of the ThreadLocalSecurityContextHolderStrategy into my own SecurityContextHolderStrategy and implement the getContext() method the way I want. But this gives me the feeling as I might be on the wrong path.

How could I achieve a "system user" Authentication as default for a new SecurityContext?

Update

My approach above is apparently not a solution as it is extremely invasive, creates redundant code and needs special treatment within the web filter chain. But it should give an understanding of my goal. I'm looking for a solution, which fits as seamless as possible to the native spring security implementation. My problem is that I'm quite fixed on the invasive approach. How can this solve nicely? I cannot imagine that I'm the first person with this requirement. Or is the whole concept altogether wrong?

like image 760
Herr Derb Avatar asked Nov 02 '17 14:11

Herr Derb


People also ask

How do I manually set an authenticated user in Spring Security?

Simply put, Spring Security hold the principal information of each authenticated user in a ThreadLocal – represented as an Authentication object. In order to construct and set this Authentication object – we need to use the same approach Spring Security typically uses to build the object on a standard authentication.

What is AuthenticationManager authenticate?

AuthenticationManager is a static class that manages the authentication modules that an application uses. When a request is made to protected resources, the AuthenticationManager calls the Authenticate method to get an Authorization instance to use in subsequent requests.

What is stored in SecurityContext?

The SecurityContext is used to store the details of the currently authenticated user, also known as a principle. So, if you have to get the username or any other user details, you need to get this SecurityContext first. The SecurityContextHolder is a helper class, which provide access to the security context.


2 Answers

If got the following solution, which is quite slick and doesn't collide or interfere with anything. In generall I have two situations where I'll have a null authentication:

  1. Main system thread.
  2. Executing scheduled task. (Could be solved with MODE_INHERITABLETHREADLOCAL config depending on use case, more details see below.)

Solution to 1.

This still leaves the problem with the main system thread. This is very easily handled by just setting the context on system start up. Also I configure the SecurityContextHolder to use a InheritableThreadLocalSecurityContextHolderStrategy so all child threads will inherit the SecurityContext. We make this setting everytime the application context refreshes. This allows to use @DirtiesContext when running security context related tests..

@Component
public class SecurityContextConfiguration {

    @EventListener
    public void setupSecurityContext(ContextRefreshedEvent event) {
    SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    SecurityContextHolder.getContext().setAuthentication(new SystemAuthentication());
    }
}

Solution to 2.

As I have configured the SecurityContextHolder with MODE_INHERITABLETHREADLOCAL. A scheduled thread will inheriet his parent Securitycontext. In my use case this is not wanted as this would mean the following: If a scheduled task gets initialized dua a user action, it would run under the users SecurityContext. As I do not want to loose a scheduled task on system reboot, I'll persist them. Which would lead to that the same task which was before initialized with the users SecurityContext, will get intitialize with the systems SecurityContext on reboot. This generates an inconsitence. Therefor I configure my scheduler too.

I simply configure the @Scheduled annotation to be executed by a DelegatingSecurityContextScheduledExecutorService allowing me to set a SecurityContext.

@EnableScheduling
@Configuration
public class SystemAwareSchedulerConfiguration implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    taskRegistrar.setScheduler(taskExecutor());
    }

    @Bean
    public ScheduledExecutorService taskExecutor() {
    ScheduledExecutorService delegateExecutor = Executors.newSingleThreadScheduledExecutor();
    SecurityContext schedulerContext = createSchedulerSecurityContext();
    return new DelegatingSecurityContextScheduledExecutorService(delegateExecutor, schedulerContext);
    }

    private SecurityContext createSchedulerSecurityContext() {
    SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
    securityContext.setAuthentication(new SystemAuthentication());
    return securityContext;
    }

}

With this two configurations, I'll always will have a SystemUser context if the thread wasn't initialized by the web container.

like image 95
Herr Derb Avatar answered Oct 05 '22 02:10

Herr Derb


Doesn't sound right to create a populated context within createEmptyContext() :o)

As it is stated here, "Once the request has been authenticated, the Authentication will usually be stored in a thread-local SecurityContext managed by the SecurityContextHolder by the authentication mechanism which is being used.", I'd rather extend UsernamePasswordAuthenticationFilter and overwrite attemptAuthentication to set the PreAuthenticatedAuthenticationToken in case of a failed username password verification.

Edit

I think for system-internal tasks it depends how/by what they are executed. For Executor, there is an example setting up the context as you described above in the thread running these executions:

@Bean
public Executor taskExecutor() {
    ScheduledExecutorService delegateExecutor = Executors.newSingleThreadScheduledExecutor();
    SecurityContext schedulerContext = createSchedulerSecurityContext();
    return new DelegatingSecurityContextScheduledExecutorService(delegateExecutor, schedulerContext);
}

private SecurityContext createSchedulerSecurityContext() {
    SecurityContext context = SecurityContextHolder.createEmptyContext();

    Authentication authentication = new PreAuthenticatedAuthenticationToken("system", null);
    authentication.setAuthenticated(true);
    context.setAuthentication(authentication);

    return context;
}

The @Configuration creating this bean implements SchedulingConfigurer.

like image 31
pma Avatar answered Oct 05 '22 04:10

pma