web-dev-qa-db-ja.com

Spring Securityと@Async(認証されたユーザーが混同)

@Asyncを使用して、Springでメソッドを非同期的に呼び出します。このメソッドは、@ PreAuthorize、Spring SecurityAnnotationでアノテーションが付けられた他のメソッドを呼び出します。承認を機能させるには、SecurityContextHolderモードをMODE_INHERITABLETHREADLOCALに設定して、認証情報が非同期呼び出しに渡されるようにする必要があります。これまでのところ、すべて正常に動作しています。

ただし、別のユーザーとしてログアウトしてログインすると、非同期メソッドでSecurityContextHolderは、ログアウトした古いユーザーの認証情報を保存します。もちろん、不要なAccessDenied例外が発生します。同期呼び出しではそのような問題はありません。

<task:executor id="executors" pool-size="10"/>を定義したので、エグゼキュータプールのスレッドが初期化されると、認証情報が上書きされないという問題がありますか?

38
Lukasz Moren

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();
            }
        }
    });
}
35
axtavt

これは将来の調査が必要なヒントです(私は疲れすぎていますが、誰かがこれが将来の調査に役立つと思うかもしれません):

今日私はつまずいた org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor 参照 GitHub

彼は、セキュリティコンテキストを委任して、@Async呼び出しを「通過」させるように設計されているようです。

この投稿もご覧ください: Spring Security 3.2 M1のハイライト、サーブレット3 APIサポート は強く関連しているようです。

13
Ralph

私もその問題に遭遇しました。 DelegatingSecurityContextAsyncTaskExecutorを使用して、ThreadPoolTask​​Executorを正しく構成することが重要です。また、initialize()メソッドを呼び出すことも重要です。そうしないと、エラーがスローされます。

// define the TaskExecutor as a bean
@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); // use this special TaskExecuter
}

// the method in your business logic which is called async
@Override
@Async("threadPoolTaskExecutor")
public void yourLogic() {
  [..]
}
7
Max

ラルフとオークからの情報を使用して-

@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メソッドを呼び出すたびに、スレッドプールスレッドが呼び出し元のスレッドと同じセキュリティコンテキストを使用する場合に機能するはずです。

6
rince

@Ralphの回答に基づいて、SpringthreadpoolingAync 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>
1
oak

@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);
        }
    }
0
Amit Parashar