SecurityContext with default System authentication

2020-03-03 07:19发布

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?

2条回答
不美不萌又怎样
2楼-- · 2020-03-03 07:56

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.

查看更多
家丑人穷心不美
3楼-- · 2020-03-03 08:05

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.

查看更多
登录 后发表回答