spring-security Spring Security和@Async(身份验证用户混淆)

x8diyxa7  于 2022-11-11  发布在  Spring
关注(0)|答案(8)|浏览(260)

我使用@Async异步调用了Spring的一个方法。这个方法调用了另一个用@PreAuthorize标注的方法,Spring Security Annotation。为了使授权工作,我必须将SecurityContextHolder模式设置为MODE_INHERITABLETHREADLOCAL,以便将身份验证信息传递给异步调用。到目前为止,一切工作正常。
然而,当我注销并以另一个用户身份登录时,SecurityContextHolder在异步方法中存储已注销的旧用户的身份验证信息。这当然会导致不希望的AccessDenied异常。同步调用则没有这样的问题。
我已经定义了<task:executor id="executors" pool-size="10"/>,那么一旦执行器池中的线程被初始化,它将不会覆盖身份验证信息,这是否是一个问题?

svdrlsy4

svdrlsy41#

我猜MODE_INHERITABLETHREADLOCAL不能正确地与线程池一起工作。
作为一种可能的解决方案,您可以尝试将ThreadPoolTaskExecutor子类化,并覆盖其方法以手动传播SecurityContext,然后声明该执行器而不是<task:executor>,如下所示:

public void execute(final Runnable r) {
    final Authentication a = SecurityContextHolder.getContext().getAuthentication();

    super.execute(new Runnable() {
        public void run() {
            try {
                SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                ctx.setAuthentication(a);
                SecurityContextHolder.setContext(ctx);
                r.run();
            } finally {
                SecurityContextHolder.clearContext();
            }
        }
    });
}
plupiseo

plupiseo2#

我 也 遇到 了 这个 问题 。 使用 DelegatingSecurityContextAsyncTaskExecutor 正确 配置 ThreadPoolTaskExecutor 是 很 重要 的 。 调用 initialize ( ) 方法 也 是 很 重要 的 , 否则 会 抛出 错误 。

@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(20);
    executor.setMaxPoolSize(1000);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setThreadNamePrefix("Async-");
    executor.initialize(); // this is important, otherwise an error is thrown
    return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}

中 的 每 一 个
业务 逻辑 中 异步 调用 的 方法 :

@Override
@Async("threadPoolTaskExecutor")
public void methodName() {
    [..]
}

格式

    • 更新 - 其他 方法 - 更多 信息 * * :如果 您 只 想 委托 安全 上下文 ( 即 身份 验证 信息 ) , 则 上述 解决 方案 可以 非常 好 地 工作 。

然而 , 在 某些 情况 下 , 您 可能 还 想 委托 其他 线程 信息 , 如 MDC 上下文 或 请求 上下文 , 或者 您 只是 想 对 如何 将 内容 传递 给 执行器 线程 拥有 更多 控制 权 。 如果 是 这种 情况 , 您 可以 手动 将 本地 线程 信息 绑定 到 执行器 线程 。 如何 实现 这 一 点 的 思想 已经 在@axtavt 的 答案 中 描述 过 了 。但是 我们 现在 可以 使用 TaskDecorator 以 更 优雅 的 方式 来 完成 它 。 TaskDecorator 将 请求 线程 的 上下文 存储 在 变量 中 , 并 将 它们 绑定 在 闭包 中 , 以便 可以 在 执行器 线程 中 访问 上下文 。 当 线程 执行 完成 时 , 上下文 将 从 执行器 线程 中 清除 ,以便 在 重用 线程 时 信息 消失 。

private static class ContextCopyingDecorator implements TaskDecorator {
    @NonNull
    @Override
    public Runnable decorate(@NonNull Runnable runnable) {
        // store context in variables which will be bound to the executor thread
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        SecurityContext securityContext = SecurityContextHolder.getContext();
        Map<String, String> mdcContextMap = MDC.getCopyOfContextMap();
        return () -> {
            try {
                // code runs inside executor thread and binds context
                RequestContextHolder.setRequestAttributes(requestAttributes);
                SecurityContextHolder.setContext(securityContext);
                MDC.setContextMap(mdcContextMap);
                runnable.run();
            } finally {
                MDC.clear();
                RequestContextHolder.resetRequestAttributes();
                SecurityContextHolder.clearContext();
            }
        };
    }
}

格式
在 TaskExecutor 创建 期间 , TaskDecorator 被 分配 给 TaskExecutor ( 使用 setTaskDecorator 方法 ) 。

@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(20);
    executor.setMaxPoolSize(1000);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setThreadNamePrefix("Async-");
    executor.setTaskDecorator(new ContextCopyingDecorator());
    return executor;
}

格式
您 也 可以 将 这 两 种 方法 结合 起来 ( 例如 , 使用 TaskDecorator 复制 MDC 上下文 , 但 仍然 使用 DelegatingSecurityContextAsyncTaskExecutor 复制 安全 上下文 ) , 但 我 不 建议 这样 做 , 因为 这 会 增加 复杂 性 。 如果 您 使用 TaskDecorator , 您 还 可以 使用 它 设置 安全 上下文 , 如果 您 只 需要 设置 安全 上下文 , 则 只需 使用 DelegatingSecurityContextAsyncTaskExecutor 方法 。

ycl3bljg

ycl3bljg3#

这只是一个提示,需要进一步调查(我太累了,但也许有人会发现这对未来的调查有用):

今天我偶然发现了org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor请参阅GitHub
它看起来像是被设计来委托安全上下文,以便通过@Async调用“传递”安全上下文。
也看看这个帖子:Spring Security 3.2 M1 Highlights, Servlet 3 API Support听起来似乎是强相关的。

tv6aics1

tv6aics14#

根据拉尔夫和奥克提供的信息-
如果您想让@Async使用标准的任务执行者标记,您可以像这样设置SpringXML配置

<task:annotation-driven executor="_importPool"/>
<task:executor id="_importPool" pool-size="5"/>

<bean id="importPool"
          class="org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor">
     <constructor-arg ref="_importPool"/>
</bean>

然后,在@Async方法中,指定要使用的池

@Async("importPool")
public void run(ImportJob import) {
   ...
}

这样,无论何时调用@Async方法,线程池线程都将使用与调用线程相同的安全上下文

esyap4oy

esyap4oy5#

如前所述,对于池化线程环境,应使用DelegatingSecurityContextAsyncTaskExecutor,而不是MODE_INHERITABLETHREADLOCAL(请阅读此处)。
为Sping Boot 项目保留简单的DelegatingSecurityContextAsyncTaskExecutor配置,这些项目将简单地使用异步任务的默认Spring Boot池:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    private final ThreadPoolTaskExecutor defaultSpringBootAsyncExecutor;

    public AsyncConfig(ThreadPoolTaskExecutor defaultSpringBootAsyncExecutor) {
        this.defaultSpringBootAsyncExecutor = defaultSpringBootAsyncExecutor;
    }

    @Override
    public Executor getAsyncExecutor() {
        return new DelegatingSecurityContextAsyncTaskExecutor(defaultSpringBootAsyncExecutor);
    }
}
ao218c7q

ao218c7q6#

基于@Ralph答案,可以通过Springthreadpooling实现Aync event,并使用http://docs.spring.io/autorepo/docs/spring-security/4.0.0.M1/apidocs/org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutor.html委托安全性

示例代码

<bean id="applicationEventMulticaster"
    class="org.springframework.context.event.SimpleApplicationEventMulticaster">
    <property name="taskExecutor">
        <ref bean="delegateSecurityAsyncThreadPool"/>
    </property>
</bean>

<bean id="threadsPool"
    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
</bean>

<bean id="delegateSecurityAsyncThreadPool"
    class="org.springframework.security.task.DelegatingSecurityContextTaskExecutor">
    <constructor-arg ref="threadsPool"/>
</bean>
o75abkj4

o75abkj47#

如果要添加到@axtavt的答案中,您也会希望覆盖其他方法。

@Override
    public <T> Future<T> submit(Callable<T> task) {
        ExecutorService executor = getThreadPoolExecutor();
        final Authentication a = SecurityContextHolder.getContext().getAuthentication();
        try {
            return executor.submit(new Callable<T>() {
                @Override
                public T call() throws Exception {
                    try {
                        SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                        ctx.setAuthentication(a);
                        SecurityContextHolder.setContext(ctx);
                        return task.call();
                    } catch (Exception e) {
                        slf4jLogger.error("error invoking async thread. error details : {}", e);
                        return null;
                    } finally {
                        SecurityContextHolder.clearContext();
                    }
                }
            });
        } catch (RejectedExecutionException ex) {
            throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
        }
    }
w41d8nur

w41d8nur8#

我使用带有JNDI管理的线程池的企业级Jboss服务器;这就是我的工作:

@Configuration
@EnableAsync
public class AsyncAppConfig {

    public static final String THREAD_POOL_ID = "threadPoolId";

    @Bean(THREAD_POOL_ID)
    public DelegatingSecurityContextAsyncTaskExecutor secureThreadPool(
            DefaultManagedTaskExecutor jbossManagedTaskExecutor) {
        return new DelegatingSecurityContextAsyncTaskExecutor(jbossManagedTaskExecutor);
    }

    @Bean
    public DefaultManagedTaskExecutor jbossManagedTaskExecutor() {
        return new DefaultManagedTaskExecutor() {
            @Override
            public void afterPropertiesSet() throws NamingException {
                // gets the ConcurrentExecutor configured by Jboss
                super.afterPropertiesSet();
                // Wraps the jboss configured ConcurrentExecutor in a securityContext aware delegate
                setConcurrentExecutor(new DelegatingSecurityContextExecutor(getConcurrentExecutor(), getContext()));
            }
        };
    }
}

相关问题