29

パイプラインにある一部の製品に対する侵入テストの結果、当時は「簡単に」修正できると思われていた問題が、難しいものであることが判明しました。

もちろん、そうすべきではありませんが、現在の新しいセッションを生成するHTTPContextだけで、なぜそれほど難しいのでしょうか? 奇妙な!とにかく、私は生意気な小さなユーティリティクラスを「ただやる」ために書きました:

(コードの書式設定/強調表示/Visual Basic についてお詫び申し上げます。何か間違ったことをしているに違いありません)


Imports System.Web
Imports System.Web.SessionState

Public Class SwitchSession

    Public Shared Sub SetNewSession(ByVal context As HttpContext)
        ' This value will hold the ID managers action to creating a response cookie
        Dim cookieAdded As Boolean
        ' We use the current session state as a template
        Dim state As HttpSessionState = context.Session
        ' We use the default ID manager to generate a new session id
        Dim idManager As New SessionIDManager()
        ' We also start with a new, fresh blank state item collection
        Dim items As New SessionStateItemCollection()
        ' Static objects are extracted from the current session context
        Dim staticObjects As HttpStaticObjectsCollection = _
            SessionStateUtility.GetSessionStaticObjects(context)
        ' We construct the replacement session for the current, some parameters are new, others are taken from previous session
        Dim replacement As New HttpSessionStateContainer( _
                 idManager.CreateSessionID(context), _
                 items, _
                 staticObjects, _
                 state.Timeout, _
                 True, _
                 state.CookieMode, _
                 state.Mode, _
                 state.IsReadOnly)
        ' Finally we strip the current session state from the current context
        SessionStateUtility.RemoveHttpSessionStateFromContext(context)
        ' Then we replace the assign the active session state using the replacement we just constructed
        SessionStateUtility.AddHttpSessionStateToContext(context, replacement)
        ' Make sure we clean out the responses of any other inteferring cookies
        idManager.RemoveSessionID(context)
        ' Save our new cookie session identifier to the response
        idManager.SaveSessionID(context, replacement.SessionID, False, cookieAdded)
    End Sub

End Class

リクエストの残りの部分では問題なく動作し、それ自体を新しいセッションとして正しく識別します (たとえばHTTPContext.Current.Session.SessionID、新しく生成されたセッション識別子を返します)。

次に驚くべきことに、次のリクエストがサーバーにヒットすると、HTTPContext.Session(HTTPSessionStateオブジェクト) は正しいSessionIDでそれ自体を識別しますが、 にIsNewSession設定されてTrueおり、空であり、前のリクエストで設定されたすべてのセッション値が失われます。

HTTPSessionStateしたがって、最初のリクエストから削除される前のオブジェクト、ここのイベント ハンドラー、そこのコールバック、複数のリクエスト間でセッション データの永続化を処理する何か、または私が欠けている何かについて何か特別なものがあるに違いありません。

共有する魔法を持っている人はいますか?

4

6 に答える 6

40

私の魔法をシェアしたいと思います。実際、いいえ、まだ魔法のようなものではありません。コードをもっとテストして進化させる必要があります。これらのコードは、Cookie を使用した InProc セッション モードでのみテストしました。これらのメソッドをページ内に配置し、ID を再生成する必要がある場所で呼び出します (Web アプリを完全な信頼に設定してください)。

void regenerateId()
{
    System.Web.SessionState.SessionIDManager manager = new System.Web.SessionState.SessionIDManager();
    string oldId = manager.GetSessionID(Context);
    string newId = manager.CreateSessionID(Context);
    bool isAdd = false, isRedir = false;
    manager.SaveSessionID(Context, newId, out isRedir, out isAdd);
    HttpApplication ctx = (HttpApplication)HttpContext.Current.ApplicationInstance;
    HttpModuleCollection mods = ctx.Modules;
    System.Web.SessionState.SessionStateModule ssm = (SessionStateModule)mods.Get("Session");
    System.Reflection.FieldInfo[] fields = ssm.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
    SessionStateStoreProviderBase store = null;
    System.Reflection.FieldInfo rqIdField = null, rqLockIdField = null, rqStateNotFoundField = null;
    foreach (System.Reflection.FieldInfo field in fields)
    {
        if (field.Name.Equals("_store")) store = (SessionStateStoreProviderBase)field.GetValue(ssm);
        if (field.Name.Equals("_rqId")) rqIdField = field;
        if (field.Name.Equals("_rqLockId")) rqLockIdField = field;
        if (field.Name.Equals("_rqSessionStateNotFound")) rqStateNotFoundField = field;
    }
    object lockId = rqLockIdField.GetValue(ssm);
    if ((lockId != null) && (oldId !=null)) store.ReleaseItemExclusive(Context, oldId, lockId);
    rqStateNotFoundField.SetValue(ssm, true);
    rqIdField.SetValue(ssm, newId);
}

私は .NET ソース コード ( http://referencesource.microsoft.com/netframework.aspxで入手可能) を掘り下げてきました。)、セッション管理メカニズムの内部をハッキングせずに SessionID を再生成する方法がないことを発見しました。つまり、SessionStateModule の内部フィールドをハックして、現在のセッションを新しい ID に保存します。現在の HttpSessionState オブジェクトには以前の Id がまだ残っている可能性がありますが、私の知る限り、SessionStateModule はそれを無視しました。状態をどこかに保存する必要がある場合は、内部の _rqId フィールドを使用するだけです。SessionStateModule を再生成 ID 機能を備えた新しいクラスにコピーするなど、他の手段を試しましたが (SessionStateModule をこのクラスに置き換えることを計画していました)、現在他の内部クラス (InProcSessionStateStore など) への参照があるため失敗しました。リフレクションを使用したハッキン​​グの欠点は、アプリケーションを「完全な信頼」に設定する必要があることです。

ああ、VB バージョンが本当に必要な場合は、これらを試してください。

Sub RegenerateID()
    Dim manager
    Dim oldId As String
    Dim newId As String
    Dim isRedir As Boolean
    Dim isAdd As Boolean
    Dim ctx As HttpApplication
    Dim mods As HttpModuleCollection
    Dim ssm As System.Web.SessionState.SessionStateModule
    Dim fields() As System.Reflection.FieldInfo
    Dim rqIdField As System.Reflection.FieldInfo
    Dim rqLockIdField As System.Reflection.FieldInfo
    Dim rqStateNotFoundField As System.Reflection.FieldInfo
    Dim store As SessionStateStoreProviderBase
    Dim field As System.Reflection.FieldInfo
    Dim lockId
    manager = New System.Web.SessionState.SessionIDManager
    oldId = manager.GetSessionID(Context)
    newId = manager.CreateSessionID(Context)
    manager.SaveSessionID(Context, newId, isRedir, isAdd)
    ctx = HttpContext.Current.ApplicationInstance
    mods = ctx.Modules
    ssm = CType(mods.Get("Session"), System.Web.SessionState.SessionStateModule)
    fields = ssm.GetType.GetFields(System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance)
    store = Nothing : rqLockIdField = Nothing : rqIdField = Nothing : rqStateNotFoundField = Nothing
    For Each field In fields
        If (field.Name.Equals("_store")) Then store = CType(field.GetValue(ssm), SessionStateStoreProviderBase)
        If (field.Name.Equals("_rqId")) Then rqIdField = field
        If (field.Name.Equals("_rqLockId")) Then rqLockIdField = field
        If (field.Name.Equals("_rqSessionStateNotFound")) Then rqStateNotFoundField = field
    Next
    lockId = rqLockIdField.GetValue(ssm)
    If ((Not IsNothing(lockId)) And (Not IsNothing(oldId))) Then store.ReleaseItemExclusive(Context, oldId, lockId)
    rqStateNotFoundField.SetValue(ssm, True)
    rqIdField.SetValue(ssm, newId)

End Sub
于 2010-12-12T02:24:05.923 に答える
3

Can Gencerが述べたように、ReleaseItemExclusiveはストアから古いセッションを削除しないため、そのセッションは最終的に期限切れになり、Global.asaxでSession_Endを呼び出します。これは、Session_End でスレッド ID をクリアしているため、本番環境で大きな問題を引き起こしました。このため、ユーザーはスレッドで自然に認証を失っていました。

以下は、動作する修正されたコードです。

Dim oHTTPContext As HttpContext = HttpContext.Current

Dim oSessionIdManager As New SessionIDManager
Dim sOldId As String = oSessionIdManager.GetSessionID(oHTTPContext)
Dim sNewId As String = oSessionIdManager.CreateSessionID(oHTTPContext)

Dim bIsRedir As Boolean = False
Dim bIsAdd As Boolean = False
oSessionIdManager.SaveSessionID(oHTTPContext, sNewId, bIsRedir, bIsAdd)

Dim oAppContext As HttpApplication = HttpContext.Current.ApplicationInstance

Dim oModules As HttpModuleCollection = oAppContext.Modules

Dim oSessionStateModule As SessionStateModule = _
  DirectCast(oModules.Get("Session"), SessionStateModule)

Dim oFields() As FieldInfo = _
  oSessionStateModule.GetType.GetFields(BindingFlags.NonPublic Or _
                                        BindingFlags.Instance)

Dim oStore As SessionStateStoreProviderBase = Nothing
Dim oRqIdField As FieldInfo = Nothing
Dim oRqItem As SessionStateStoreData = Nothing
Dim oRqLockIdField As FieldInfo = Nothing
Dim oRqStateNotFoundField As FieldInfo = Nothing

For Each oField As FieldInfo In oFields
    If (oField.Name.Equals("_store")) Then
        oStore = DirectCast(oField.GetValue(oSessionStateModule), _
                            SessionStateStoreProviderBase)
    End If
    If (oField.Name.Equals("_rqId")) Then
        oRqIdField = oField
    End If
    If (oField.Name.Equals("_rqLockId")) Then
        oRqLockIdField = oField
    End If
    If (oField.Name.Equals("_rqSessionStateNotFound")) Then
        oRqStateNotFoundField = oField
    End If
    If (oField.Name.Equals("_rqItem")) Then
        oRqItem = DirectCast(oField.GetValue(oSessionStateModule), _
                             SessionStateStoreData)
    End If
Next

If oStore IsNot Nothing Then

    Dim oLockId As Object = Nothing

    If oRqLockIdField IsNot Nothing Then
        oLockId = oRqLockIdField.GetValue(oSessionStateModule)
    End If

    If (oLockId IsNot Nothing) And (Not String.IsNullOrEmpty(sOldId)) Then
        oStore.ReleaseItemExclusive(oHTTPContext, sOldId, oLockId)
        oStore.RemoveItem(oHTTPContext, sOldId, oLockId, oRqItem)
    End If

    oRqStateNotFoundField.SetValue(oSessionStateModule, True)
    oRqIdField.SetValue(oSessionStateModule, sNewId)

End If
于 2015-03-17T11:46:13.883 に答える
0

設定するだけではできません:

<sessionState regenerateExpiredSessionId="False" />

web.config で、Ahmad によって提案されたソリューションを使用しますか?

于 2009-09-10T13:34:12.420 に答える
0

HttpSessionState.Abandon メソッドの使用を検討しましたか? それはすべてをクリアするはずです。次に、新しいセッションを開始し、上記のコードから保存したすべてのアイテムを入力します。

Session.Abandon();十分なはずです。それ以外の場合は、まだ頑固な場合は、さらにいくつかの呼び出しでさらに一歩進んでみることができます。

Session.Contents.Abandon();
Session.Contents.RemoveAll(); 
于 2009-09-02T15:46:18.937 に答える