具有默认系统 authentication/user 的 SecurityContext
SecurityContext with default System authentication/user
在我的 spring 应用程序中,我希望 SecurityContext
始终包含 Authentication
。如果它不是常规的 UsernamePasswordAuthenticationToken
,它将是描述 "system user." 的 PreAuthenticatedAuthenticationToken
这在需要用户的不同系统功能中有原因。如果没有用户上下文,为了避免特殊处理,我只想添加系统上下文。恕我直言,这也与单一责任原则有关。
为了实现这一点,我可以简单地实现我自己的 SecurityContextHolderStrategy
并将其设置为 SecurityContextHolder
和 SecurityContextHolder.setStrategyName(MyStrategyClassName);
现在问题来了:
默认的 SecurityContextHolderStrategy
是 ThreadLocalSecurityContextHolderStrategy
。我对这个策略及其运作方式感到满意。我唯一要改变的是 getContext()
方法。
public SecurityContext getContext() {
SecurityContext ctx = CONTEXT_HOLDER.get();
if (ctx == null) {
ctx = createEmptyContext();
CONTEXT_HOLDER.set(ctx);
}
return ctx;
}
到
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;
}
这是不可能的,因为ThreadLocalSecurityContextHolderStrategy
class是而不是public
。当然,我可以简单地将 ThreadLocalSecurityContextHolderStrategy
的代码复制粘贴到我自己的 SecurityContextHolderStrategy
中,然后按我想要的方式实现 getContext()
方法。但这给我的感觉是我可能走错了路。
我怎样才能将 "system user" Authentication
作为新 SecurityContext
的默认设置?
更新
我上面的方法显然不是解决方案,因为它极具侵入性,会创建冗余代码并且需要在网络过滤器链中进行特殊处理。但它应该让我了解我的目标。
我正在寻找一种解决方案,它尽可能无缝地适应本机 spring 安全实施。
我的问题是我非常喜欢侵入性方法。这怎么能很好地解决呢?我无法想象我是第一个有这个要求的人。或者整个概念完全错误?
在 createEmptyContext()
中创建填充上下文听起来不对 :o)
正如 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." 所述,我宁愿扩展 UsernamePasswordAuthenticationFilter
并覆盖 attemptAuthentication
以设置 PreAuthenticatedAuthenticationToken
,以防万一用户名密码验证失败。
编辑
我认为对于系统内部任务,它取决于 how/by 它们执行的内容。
对于 Executor
,有一个 example 设置上下文,正如您在线程 运行 中描述的那样,这些执行:
@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;
}
创建此 bean 的 @Configuration
实现 SchedulingConfigurer
。
如果得到以下解决方案,它非常圆滑,不会碰撞或干扰任何东西。
一般来说,我有两种情况需要 null
身份验证:
- 主系统线程。
- 正在执行计划任务。 (可以根据用例使用
MODE_INHERITABLETHREADLOCAL
配置来解决,更多细节见下文。)
1.
的解法
这仍然是主系统线程的问题。这很容易通过在系统启动时设置上下文来处理。此外,我将 SecurityContextHolder
配置为使用 InheritableThreadLocalSecurityContextHolderStrategy
,因此所有子线程都将继承 SecurityContext
。每次应用程序上下文刷新时,我们都会进行此设置。这允许在 运行 安全上下文相关测试时使用 @DirtiesContext
..
@Component
public class SecurityContextConfiguration {
@EventListener
public void setupSecurityContext(ContextRefreshedEvent event) {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
SecurityContextHolder.getContext().setAuthentication(new SystemAuthentication());
}
}
2的解决方案
因为我已将 SecurityContextHolder
配置为 MODE_INHERITABLETHREADLOCAL。预定线程将继承其父线程 Securitycontext
。在我的用例中,这是不需要的,因为这意味着以下内容:
如果计划任务通过用户操作初始化,它将在用户 SecurityContext
下 运行。因为我不想在系统重启时丢失计划任务,所以我会保留它们。这将导致之前由用户 SecurityContext
初始化的相同任务将在重新启动时由系统 SecurityContext
初始化。这会产生不一致。因此,我也配置了我的调度程序。
我只是将 @Scheduled
注释配置为由 DelegatingSecurityContextScheduledExecutorService
执行,允许我设置 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;
}
}
使用这两种配置,如果线程未被 Web 容器初始化,我将始终拥有一个 SystemUser 上下文。
在我的 spring 应用程序中,我希望 SecurityContext
始终包含 Authentication
。如果它不是常规的 UsernamePasswordAuthenticationToken
,它将是描述 "system user." 的 PreAuthenticatedAuthenticationToken
这在需要用户的不同系统功能中有原因。如果没有用户上下文,为了避免特殊处理,我只想添加系统上下文。恕我直言,这也与单一责任原则有关。
为了实现这一点,我可以简单地实现我自己的 SecurityContextHolderStrategy
并将其设置为 SecurityContextHolder
和 SecurityContextHolder.setStrategyName(MyStrategyClassName);
现在问题来了:
默认的 SecurityContextHolderStrategy
是 ThreadLocalSecurityContextHolderStrategy
。我对这个策略及其运作方式感到满意。我唯一要改变的是 getContext()
方法。
public SecurityContext getContext() {
SecurityContext ctx = CONTEXT_HOLDER.get();
if (ctx == null) {
ctx = createEmptyContext();
CONTEXT_HOLDER.set(ctx);
}
return ctx;
}
到
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;
}
这是不可能的,因为ThreadLocalSecurityContextHolderStrategy
class是而不是public
。当然,我可以简单地将 ThreadLocalSecurityContextHolderStrategy
的代码复制粘贴到我自己的 SecurityContextHolderStrategy
中,然后按我想要的方式实现 getContext()
方法。但这给我的感觉是我可能走错了路。
我怎样才能将 "system user" Authentication
作为新 SecurityContext
的默认设置?
更新
我上面的方法显然不是解决方案,因为它极具侵入性,会创建冗余代码并且需要在网络过滤器链中进行特殊处理。但它应该让我了解我的目标。 我正在寻找一种解决方案,它尽可能无缝地适应本机 spring 安全实施。 我的问题是我非常喜欢侵入性方法。这怎么能很好地解决呢?我无法想象我是第一个有这个要求的人。或者整个概念完全错误?
在 createEmptyContext()
中创建填充上下文听起来不对 :o)
正如 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." 所述,我宁愿扩展 UsernamePasswordAuthenticationFilter
并覆盖 attemptAuthentication
以设置 PreAuthenticatedAuthenticationToken
,以防万一用户名密码验证失败。
编辑
我认为对于系统内部任务,它取决于 how/by 它们执行的内容。
对于 Executor
,有一个 example 设置上下文,正如您在线程 运行 中描述的那样,这些执行:
@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;
}
创建此 bean 的 @Configuration
实现 SchedulingConfigurer
。
如果得到以下解决方案,它非常圆滑,不会碰撞或干扰任何东西。
一般来说,我有两种情况需要 null
身份验证:
- 主系统线程。
- 正在执行计划任务。 (可以根据用例使用
MODE_INHERITABLETHREADLOCAL
配置来解决,更多细节见下文。)
1.
的解法这仍然是主系统线程的问题。这很容易通过在系统启动时设置上下文来处理。此外,我将 SecurityContextHolder
配置为使用 InheritableThreadLocalSecurityContextHolderStrategy
,因此所有子线程都将继承 SecurityContext
。每次应用程序上下文刷新时,我们都会进行此设置。这允许在 运行 安全上下文相关测试时使用 @DirtiesContext
..
@Component
public class SecurityContextConfiguration {
@EventListener
public void setupSecurityContext(ContextRefreshedEvent event) {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
SecurityContextHolder.getContext().setAuthentication(new SystemAuthentication());
}
}
2的解决方案
因为我已将 SecurityContextHolder
配置为 MODE_INHERITABLETHREADLOCAL。预定线程将继承其父线程 Securitycontext
。在我的用例中,这是不需要的,因为这意味着以下内容:
如果计划任务通过用户操作初始化,它将在用户 SecurityContext
下 运行。因为我不想在系统重启时丢失计划任务,所以我会保留它们。这将导致之前由用户 SecurityContext
初始化的相同任务将在重新启动时由系统 SecurityContext
初始化。这会产生不一致。因此,我也配置了我的调度程序。
我只是将 @Scheduled
注释配置为由 DelegatingSecurityContextScheduledExecutorService
执行,允许我设置 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;
}
}
使用这两种配置,如果线程未被 Web 容器初始化,我将始终拥有一个 SystemUser 上下文。