7

この質問に記載されている理由で、ランダムなトークンを生成しています。これらはa に配置され、java.util.ListこれListはセッション スコープに保持されます。

いくつかの Google 検索を行った後List、セッションでこれに含まれるすべての要素 (トークン) を 1 時間ごとに削除することにしました。

Quartz APIを使用することも考えられますが、ユーザーのセッションを操作することはできません。Spring で Quartz API (1.8.6、2.x は、私が使用している Spring 3.2 と互換性がありません) で試したことは、以下のとおりです。

package quartz;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public final class RemoveTokens extends QuartzJobBean
{    
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException
    {
        System.out.println("QuartzJobBean executed.");
    }
}

そして、それはapplication-context.xml次のようにファイルに構成されました。

<bean name="removeTokens" class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass" value="quartz.RemoveTokens" />
    <property name="jobDataAsMap">
        <map>
            <entry key="timeout" value="5" />
        </map>
    </property>
</bean>

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
      <property name="jobDetail" ref="removeTokens"/>
      <property name="startDelay" value="10000"/>
      <property name="repeatInterval" value="3600000"/>
</bean>

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="triggers">
      <list>
          <ref bean="simpleTrigger" />
      </list>
  </property>
</bean>

クラスのオーバーライドされたメソッドは、RemoveTokensXML で設定された 10 秒の初期間隔で 1 時間ごとに実行されますが、一部のクラスの一部のメソッドを実行してList、ユーザーのセッションに保存されている で使用可能なトークンを削除することはできません。出来ますか?

List定義された時間間隔(1時間ごと)でセッションスコープに保存されているこれを削除する公正な方法は何ですか? この Quartz API を使用してそれが可能になると、はるかに優れたものになります。


編集:

以下の回答によると、次のことを試しましたが、残念ながら違いはありませんでした。

application-context.xmlファイルでは、

<task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>
<task:executor id="taskExecutor" pool-size="5"/>
<task:scheduler id="taskScheduler" pool-size="10"/>

これには、次の追加の名前空間が必要です。

xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/task
                    http://www.springframework.org/schema/task/spring-task-3.2.xsd"

次の Bean は、セッション スコープ Bean として登録されました。

package token;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
//@Scope("session")
public final class SessionToken implements SessionTokenService
{
    private List<String> tokens;

    private static String nextToken()
    {
        long seed = System.currentTimeMillis(); 
        Random r = new Random();
        r.setSeed(seed);
        return Long.toString(seed) + Long.toString(Math.abs(r.nextLong()));
    }

    @Override
    public boolean isTokenValid(String token)
    {        
        return tokens==null||tokens.isEmpty()?false:tokens.contains(token);
    }

    @Override
    public String getLatestToken()
    {
        if(tokens==null)
        {
            tokens=new ArrayList<String>(0);
            tokens.add(nextToken());            
        }
        else
        {
            tokens.add(nextToken());
        }

        return tokens==null||tokens.isEmpty()?"":tokens.get(tokens.size()-1);
    }

    @Override
    public boolean unsetToken(String token)
    {                
        return !StringUtils.isNotBlank(token)||tokens==null||tokens.isEmpty()?false:tokens.remove(token);
    }

    @Override
    public void unsetAllTokens()
    {
        if(tokens!=null&&!tokens.isEmpty())
        {
            tokens.clear();
        }
    }
}

そしてそれが実装するインターフェース、

package token;

import java.io.Serializable;

public interface SessionTokenService extends Serializable
{
    public boolean isTokenValid(String token);
    public String getLatestToken();
    public boolean unsetToken(String token);
    public void unsetAllTokens();
}

そして、この Bean はapplication-context.xml次のようにファイルに構成されました。

<bean id="sessionTokenCleanerService" class="token.SessionToken" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

現在、このサービスを次のクラスに注入しています。

package token;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public final class PreventDuplicateSubmission
{    
    @Autowired
    private final SessionTokenService sessionTokenService=null;

    @Scheduled(fixedDelay=3600000)
    public void clearTokens()
    {
        System.out.println("Scheduled method called.");
        sessionTokenService.unsetAllTokens();            
    }
}

そして、application-context.xmlファイルでは、

<bean id="preventDuplicateSubmissionService" class="token.PreventDuplicateSubmission"/>

上記の Bean は両方とも で注釈が付けられており、ファイル (またはその名前は何でも)の@Service一部である必要があります。context:component-scandispatacher-servelt.xml

前のコード スニペットの注釈で注釈が付けられたメソッド@Scheduledは、指定されたレートで定期的に呼び出されますが、次の明らかな例外がスローされます。

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.sessionTokenCleanerService': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:343)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:33)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:184)
    at $Proxy79.unsetAllTokens(Unknown Source)
    at token.PreventDuplicateSubmission.clearTokens(PreventDuplicateSubmission.java:102)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:64)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:53)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
    at org.springframework.web.context.request.SessionScope.get(SessionScope.java:90)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:329)
    ... 19 more

ユーザーのセッションに保存されているデータを消去するには、このタスクを実行するメソッドを、すべてのユーザーのセッションから定義された時間間隔で定期的に呼び出す必要がありますが、この試みには当てはまりません。方法は何ですか?質問は単純かもしれません: すべてのユーザーのセッションから定期的な時間間隔をトリガーする方法は? Spring または Servlet API は、これを達成するために何かをサポートしていますか?

4

7 に答える 7

9

コメント

再送信を防ぐためにトークンを使用することをお勧めします (「Core J2EE Patterns」という本の「Introduce Synchronizing Token」リファクタリング)。:^)

Quartz は、複雑または正確なスケジューリングに役立ちます。しかし、要件には Quartz は必要ありません。CDI、java.util.Timer、ScheduledExecutor、および/または EJB タイマーを学習するとより役立つ場合があります。

あなたが言及したように、スケジューラを使用する場合は、ユーザーセッションごとにスケジューラとスレッドインスタンスを使用するのではなく、すべてのユーザーがシングルトンスケジューラを共有することをお勧めします。

HttpSession への参照を保存したり、パラメーターとして渡したりする場合は注意してください。参照を保存すると、セッションが完了したときにガベージ コレクションが防止され、(大きな?) メモリ リークが発生します。HttpSessionListener やその他のトリックを使用して参照をクリーンアップしようとすると、機能しない可能性があり、面倒です。HttpSession をパラメーターとしてメソッドに渡すと、複雑な HttpSession オブジェクトをモックする必要があるため、サーブレット コンテナーに人為的に依存するようになり、単体テストが難しくなります。すべてのセッション データを単一のオブジェクト UserSessionState にラップする方がクリーンです。これへの参照をセッションおよびオブジェクト インスタンス変数に格納します。これはコンテキスト オブジェクト パターンとも呼ばれます。つまり、HTTP プロトコル クラスから独立して、すべてのスコープ データを 1 つまたは少数の POJO コンテキスト オブジェクトに格納します。

答え

あなたの問題に対する2つの代替解決策を提案します:

  1. java.util.Timerシングルトン インスタンスを使用する

    java.util.Timer(Java SE 1.3 で導入された) を(Java SE 5 で導入された) に置き換えるScheduledExecutorと、ほぼ同じソリューションが得られます。

    これは JVM で利用できます。jar のセットアップも構成も必要ありません。を呼び出しtimerTask.scheduleて、TimerTaskインスタンスを渡します。スケジュールの期日になると、timerTask.runが呼び出されます。timerTask.cancelおよびtimerTask.purge使用済みメモリを解放 することで、スケジュールされたタスクを削除します。

    コンストラクターを含めて をコーディングし、新しいインスタンスを作成してスケジュール メソッドに渡します。つまり、必要なデータまたはオブジェクト参照を;TimerTask内に格納できます。TimerTaskそれへの参照を保持し、後でいつでも setter メソッドを呼び出すことができます。

    Timerインスタンスとインスタンスの 2 つのグローバル シングルトンを作成することをお勧めしTimerTaskます。カスタムTimerTaskインスタンスで、すべてのユーザー セッションのリストを保持します (UserSessionState形式ではなく、または Spring/CDI Bean のような POJO 形式でHttpSession)。このクラスに次の 2 つのメソッドを追加addSessionObjectremoveSessionObjectますUserSessionState。このTimerTask.runメソッドでは、一連のデータを繰り返し処理しUserSessionState、データをクリアします。

    カスタム HttpSessionListener を作成します - sessionCreated から、新しい UserSessionState インスタンスをセッションに入れ、TimerTask.addUserSession を呼び出します。sessionDestroyed から、TimerTask.removeUserSession を呼び出します。

    グローバル スコープのシングルトンtimer.scheduleを呼び出して、TimerTask インスタンスをスケジュールし、セッション スコープの参照の内容をクリアします。

  2. 上限のあるサイズのトークン リストを使用する (クリーンアップなし)

    経過時間に基づいてトークンをクリーンアップしないでください。代わりに、リストのサイズ (たとえば 25 トークン) を制限し、最近生成されたトークンを保存します。

    これはおそらく最も簡単な解決策です。リストに要素を追加するときは、最大サイズを超えていないかどうかを確認します。超えている場合は、ラップアラウンドして、リストの最初のインデックスから挿入します。

    if (++putIndex > maxSize) {
        putIndex = 0;
    }
    list.put(putIndex, newElement);
    

    ここでは、スケジューラは必要ありません。また、すべてのユーザー セッションのセットを形成および維持する必要もありません。

于 2013-04-23T06:02:33.143 に答える
3

これはもっとシンプルにすべきだと思います。

Synchronizer Token の実装について

  1. シンクロナイザー トークン パターンのトークンは、再利用できるようには意図されていません。トークンは、1 回の送信に対してのみ有効と見なされます。それ以上でもそれ以下でもありません。

  2. 任意の時点で、セッションに対して 1 つのトークンのみを保存する必要があります。

  3. フォームがユーザーに表示されるとき、トークンは非表示のフォーム要素としてフォームに含まれている必要があります。

  4. 送信時に、フォーム内のトークンとセッションが一致するかどうかを確認するだけです。その場合は、フォームの送信を許可し、セッションでトークンの値をリセットします。

  5. ユーザーが同じフォームを (古いトークンで) 再送信すると、トークンが一致しなくなり、二重送信または古い送信を検出できるようになりました

  6. 一方、ユーザーがフォーム自体をリロードすると、更新されたトークンが非表示のフォーム要素に表示されます。

結論 - ユーザーのトークンのリストを保存する必要はありません。セッションごとに 1 つのトークンが必要です。このパターンは、CSRF 攻撃を防ぐためのセキュリティ対策として広く使用されています。ページ上の各リンクを 1 回だけ呼び出すことができる場所。これも、セッションごとに 1 つのトークンで実行できます。参考までに、CSRF Guard V3 がどのように機能するかを確認できますhttps://www.owasp.org/index.php/Category:OWASP_CSRFGuard_Project#Source_Code

セッションについて

  1. セッション オブジェクトは、実行のスレッドが何らかの形で http 要求/応答のペアに関連付けられている場合にのみ意味を持ちます。または簡単に言えば、ユーザーがあなたのサイトを閲覧している限り。

  2. ユーザーがいなくなると、セッションも (JVM から) なくなります。したがって、タイマーを使用してリセットすることはできません

  3. セッションはサーバーによってシリアル化され、ユーザーがサイトに再度アクセスしたときに確実に起動できるようになります (jsessionid は、どのブラウザー セッションに対してどのセッションを逆シリアル化する必要があるかを識別するために使用されます)。

  4. セッションにはタイムアウトが関連付けられています。タイムアウトが期限切れになると、ユーザーが再アクセスしたときにサーバーが新しいセッションを開始します。

結論 - ユーザーのセッションを定期的にフラッシュしなければならないもっともらしい理由はありません - そしてそれを行う方法はありません。

何か誤解した場合はお知らせください。これがお役に立てば幸いです。

于 2013-04-23T10:41:34.807 に答える