2012-02-13 9 views
5

¿Cómo resolvería el problema de concurrencia con el siguiente código? En este ejemplo, nos gustaría saber por qué el usuario ha fallado la autenticación. El problema es que este código hace dos llamadas separadas a la base de datos, pero nos gustaría que todo el método ocurra dentro de una transacción conceptual. Específicamente estamos interesados ​​en isolation. No queremos que las escrituras simultáneas durante la ejecución de este método afecten nuestras lecturas antes de que hayamos determinado el motivo del error de autenticación.Problemas de concurrencia con múltiples transacciones de bases de datos independientes?

Algunas soluciones vienen a la mente: Thread Locking, TransactionScope y Optimistic Locking. Realmente me gusta la idea de Optimistic Locking, ya que creo que los conflictos son raros, pero no hay nada integrado en .NET para hacer esto, ¿verdad?

Además, ¿hay algo de lo que DE VERDAD estar preocupado en este caso? ¿Cuándo es importante tener en cuenta los problemas de concurrencia y cuándo no? ¿Qué debe considerarse al implementar una solución? ¿Actuación? La duración del bloqueo? ¿Cuán probables son los conflictos?

Editar: Después de revisar respuesta Aristos', creo que lo que estoy realmente después es algún tipo de nivel de aislamiento 'snapshot' para el método de autenticación.

public MembershipStatus Authenticate(string username, string password) 
    { 
     MembershipUser user = Membership.GetUser(username); 
     if (user == null) 
     { 
      // user did not exist as of Membership.GetUser 
      return MembershipStatus.InvalidUsername; 
     } 

     if (user.IsLockedOut) 
     { 
      // user was locked out as of Membership.GetUser 
      return MembershipStatus.AccountLockedOut; 
     } 

     if (Membership.ValidateUser(username, password)) 
     { 
      // user was valid as of Membership.ValidateUser 
      return MembershipStatus.Valid; 
     } 

     // user was not valid as of Membership.ValidateUser BUT we don't really 
     // know why because we don't have ISOLATION. The user's status may have changed 
     // between the call to Membership.GetUser and Membership.ValidateUser. 
     return MembershipStatus.InvalidPassword; 
    } 
+1

No estoy seguro de que realmente necesita preocuparse por el aislamiento aquí. ¿Cuáles son las posibilidades de que un usuario sea eliminado o bloqueado entre la llamada a 'GetUser' y' ValidateUser'? Y más al punto, no debes exponer esta información al usuario de todos modos. Si muestra diferentes mensajes para "contraseña incorrecta" frente a "usuario no existe" frente a "cuenta bloqueada", alguien que intente hackear su sitio puede aprovechar esa información para obtener una lista de nombres de usuario válidos.No desea eso, ya que esos nombres de usuario pueden ser útiles para explotar alguna otra vulnerabilidad en su infraestructura. –

+0

+1 para la respuesta. Probablemente usado un mal ejemplo aquí (aunque yo estaba pensando en la distinción entre "credenciales no válidas" y "Cuenta bloqueada" y tal vez tengo que reconsiderar eso). ¡Simplemente no me gusta el hecho de que los datos puedan cambiar debajo de mí! La concurrencia es difícil! ¿Qué sucede si NECESITO que los datos sean constantes, qué sugeriría? – Brandon

Respuesta

1

Basado en mi lectura here y here, parece como un System.Transactions.TransactionScope envolver su método de toda debe dar de alta automáticamente las llamadas de base de datos en una transacción común, lo que resulta en seguridad transaccional en todo el alcance de la transacción.

Lo que quiere hacer algo como esto:

public MembershipStatus Authenticate(string username, string password) 
{ 
    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot })) 
    { 
    MembershipUser user = Membership.GetUser(username); 
     if (user == null) 
     { 
      // user did not exist as of Membership.GetUser 
      return MembershipStatus.InvalidUsername; 
     } 

     if (user.IsLockedOut) 
     { 
      // user was locked out as of Membership.GetUser 
      return MembershipStatus.AccountLockedOut; 
     } 

     if (Membership.ValidateUser(username, password)) 
     { 
      // user was valid as of Membership.ValidateUser 
      return MembershipStatus.Valid; 
     } 

     // user was not valid as of Membership.ValidateUser BUT we don't really 
     // know why because we don't have ISOLATION. The user's status may have changed 
     // between the call to Membership.GetUser and Membership.ValidateUser. 
     return MembershipStatus.InvalidPassword; 
    } 
} 
+0

+1 por tomarse el tiempo para responder. ¿Por qué tendría que implementar mi propio proveedor de membresía? ¿No podría simplemente usar la clase TransactionScope "en la cual la infraestructura administra automáticamente las transacciones"? – Brandon

+0

@Brandon haber tardado tanto tiempo en respuesta- que quería hacer una investigación antes de responder a esta. Actualicé mi respuesta para reflejar el uso de TransactionScope. –

+0

gracias por la respuesta. Estoy marcando esto como la respuesta aceptada, sobre todo para su primer comentario a mi pregunta (probablemente no debería estar haciendo esto para empezar, y aunque quisiera, realmente no es un problema). – Brandon

1

voy a utilizar mutex usando el nombre como parámetro de bloqueo, por lo que sólo el mismo usuario puede puede bloquear por un tiempo. Esto para mí es más seguro para una computadora porque con mutex puedo capturar todos los hilos posibles de diferentes grupos o llamadas web.

public MembershipStatus AuthenticateLock(string username, string password) 
{ 
    if(string.IsNullOrEmpty(username)) 
     return MembershipStatus.InvalidUsername; 

    // TODO: Here you must check and clear for non valid characters on mutex name 
    using (var mutex = new Mutex (false, username)) 
    { 
     // possible lock and wait, more than 16 seconds and the user can go... 
     mutex.WaitOne (TimeSpan.FromSeconds(16), false); 

     // here I call your function anyway ! and what ever done... 
     // at least I get a result 
     return Authenticate(username, password) 
    } 
} 

Más comentarios: Tanto Membership.ValidateUser y Membership.GetUser Realizar una llamada a la base de datos.

Pero si se utiliza la sesión asp.net estándar de las páginas que componen este llamadas y afectan a este parámetros, entonces el página muestra todos bloqueo listo el otro, así que creo que no hay ninguna posibilidad de necesitar este llamada mutex Porque el bloqueo de la sesión es suficiente para sincronizar y esta parte. Recuerdo que la sesión está bloqueando la página desde el principio hasta el final para todos los usuarios.

Acerca de bloqueo de sesión: Replacing ASP.Net's session entirely

jQuery Ajax calls to web service seem to be synchronous

+0

1 por tomarse el tiempo para responder a esta pregunta y para la información de sesión. Entonces, estás sugiriendo el bloqueo de subprocesos. bloqueo de rosca proporciona una especie de aislamiento "suave", pero no será útil si usted tiene múltiples procesos (no temas) intentar acceder a un recurso compartido. Creo que lo que realmente busco es algún tipo de nivel de aislamiento de "instantáneas" para el método Autenticar. – Brandon

+1

@Brandon bloquear el mutex que uso aquí es servicial para múltiples procesos!. Pero digo que ni siquiera necesita esto, si usa sesión en todas sus llamadas a esta función. Bloqueo de sesión listo para las páginas, por lo que no necesita un segundo bloqueo. – Aristos

+0

¡Interesante! No sabía que "Los mutexes del sistema designado son visibles en todo el sistema operativo y se pueden usar para sincronizar las actividades de los procesos". Así que supongo que el límite es la PC/OS, p. no puede sincronizar procesos con un mutex en varias PC/OS? – Brandon

Cuestiones relacionadas