web-dev-qa-db-fra.com

Génération d'une nouvelle session ASP.NET dans le HTTPContext actuel

À la suite d’un test de pénétration effectué sur certains de nos produits en cours de développement, il semblait qu’à l’époque un problème «facile» à résoudre soit en train de devenir un problème difficile à résoudre. 

Non pas que cela devrait bien sûr, je veux dire pourquoi juste générer une toute nouvelle session pour la HTTPContext actuelle serait si difficile? Bizarre! Quoi qu'il en soit, j'ai écrit une petite classe utilitaire effrontée pour "il suffit de le faire": 

(excuses pour le formatage/la mise en surbrillance du code/Visual Basic I doit fait quelque chose de mal)


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

Cela fonctionne correctement pour le reste de la demande et s’identifie correctement en tant que nouvelle session (par exemple, HTTPContext.Current.Session.SessionID renvoie le nouvel identifiant de session généré). 

Surprise, alors, lorsque la prochaine requête arrive sur le serveur, le HTTPContext.Session (un objet HTTPSessionState) s'identifie avec la SessionID correcte, mais a IsNewSession défini sur True et est vide, perdant toutes les valeurs de session définies dans la demande précédente.

Il doit donc y avoir quelque chose de spécial dans le précédent objet HTTPSessionState en cours de suppression de la demande initiale, un gestionnaire d'événements ici, un rappel là-bas, quelque chose qui gère la persistance des données de session lors de demandes, ou tout simplement quelque chose qui me manque?

Quelqu'un a une magie à partager?

25
Rabid

J'aimerais partager ma magie. En fait, non, ce n'est pas encore magique .. Nous devrions tester et faire évoluer davantage le code. Je n'ai testé ce code qu'en mode de session avec cookie, InProc. Insérez cette méthode dans votre page et appelez-la à l'endroit où vous souhaitez que l'ID soit régénéré (définissez votre application Web sur Full Trust):

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);
}

J'ai fouillé dans le code source .NET (disponible dans http://referencesource.Microsoft.com/netframework.aspx ) et découvert qu'il était impossible de régénérer SessionID sans pirater les éléments internes de la session. mécanisme de gestion. Donc, je ne fais que cela - pirater les champs internes de SessionStateModule, afin que la Session en cours soit sauvegardée sous un nouvel ID. Peut-être que l'objet HttpSessionState actuel a toujours l'identifiant précédent, mais autant que je sache, SessionStateModule l'a ignoré. Il utilise simplement le champ interne _rqId lorsqu'il doit sauvegarder l'état quelque part. J'ai essayé d'autres moyens, tels que la copie de SessionStateModule dans une nouvelle classe avec une fonctionnalité de régénération d'ID (je prévoyais de remplacer SessionStateModule par cette classe), mais j'ai échoué car il contient actuellement des références à d'autres classes internes (comme InProcSessionStateStore). L'inconvénient du piratage par réflexion est que nous devons paramétrer notre application sur «Full Trust».

Oh, et si vous avez vraiment besoin de la version VB, essayez ceci:

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
33
YudhiWidyatama

Comme Can Gencer l'a mentionné - ReleaseItemExclusive ne supprime pas l'ancienne session du magasin, ce qui entraîne son expiration et l'appel de Session_End dans Global.asax. Cela nous causait un énorme problème en production, car nous effacions l'identité du fil dans Session_End et, pour cette raison, les utilisateurs perdaient spontanément leur authentification sur le fil.

Donc, ci-dessous est le code corrigé qui fonctionne.

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
2
Max Ostrinsky

Si vous êtes soucieux de la sécurité et souhaitez que la version C # de cette réponse supprime l'ancien champ, veuillez utiliser les éléments suivants.

private static void RegenerateSessionId()
{

    // Initialise variables for regenerating the session id
    HttpContext Context = HttpContext.Current;
    SessionIDManager manager = new SessionIDManager();
    string oldId = manager.GetSessionID(Context);
    string newId = manager.CreateSessionID(Context);
    bool isAdd = false, isRedir = false;

    // Save a new session ID
    manager.SaveSessionID(Context, newId, out isRedir, out isAdd);

    // Get the fields using the below and create variables for storage
    HttpApplication ctx = HttpContext.Current.ApplicationInstance;
    HttpModuleCollection mods = ctx.Modules;
    SessionStateModule ssm = (SessionStateModule)mods.Get("Session");
    FieldInfo[] fields = ssm.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
    SessionStateStoreProviderBase store = null;
    FieldInfo rqIdField = null, rqLockIdField = null, rqStateNotFoundField = null;
    SessionStateStoreData rqItem = null;

    // Assign to each variable the appropriate field values
    foreach (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;
        if (field.Name.Equals("_rqItem")) rqItem = (SessionStateStoreData)field.GetValue(ssm);
    }

    // Remove the previous session value
    object lockId = rqLockIdField.GetValue(ssm);
    if ((lockId != null) && (oldId != null))
        store.RemoveItem(Context, oldId, lockId, rqItem);

    rqStateNotFoundField.SetValue(ssm, true);
    rqIdField.SetValue(ssm, newId);
}
1
DubDub

Pour MVC4, veuillez avoir ce code:

 System.Web.SessionState.SessionIDManager manager = new System.Web.SessionState.SessionIDManager();
            HttpContext Context = System.Web.HttpContext.Current;
            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)System.Web.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);
0
arunprakash

Ne pouvez-vous pas simplement définir:

<sessionState regenerateExpiredSessionId="False" />

dans web.config, puis utilisez la solution proposée par Ahmad?

0
Jason

Avez-vous envisagé d'utiliser la méthode HttpSessionState.Abandon ? Cela devrait tout effacer. Ensuite, démarrez une nouvelle session et remplissez-la avec tous les éléments stockés dans votre code ci-dessus.

Session.Abandon(); devrait suffire. Sinon, vous pourriez essayer de faire un effort supplémentaire avec quelques appels de plus si le problème persiste:

Session.Contents.Abandon();
Session.Contents.RemoveAll(); 
0
Ahmad Mageed