问题
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?
回答1:
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:
- Main system thread.
- 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.
回答2:
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
.
来源:https://stackoverflow.com/questions/47078381/securitycontext-with-default-system-authentication-user