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?
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.
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.
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.
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:
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With