Ejemplo de validación Ajax con BotDetect CAPTCHA ASP

Este ejemplo demuestra como usar consultas asincrónicas Ajax para mejorar la experiencia de usuario al validar los códigos CAPTCHA. Se utiliza la biblioteca jQuery para todas las consultas asincrónicas y también para clasificar los resultados JSON devueltos por la validación. Cuando el usuario escribe un código CAPTCHA erróneo, el mensaje de error es desplegado notablemente más rápido. De todas maneras, y por motivos de seguridad, la validación del lado del ciente del CAPTCHA no evita la validación del lado del servidor de forma posterior.

Ubicación de los archivos del proyecto de ejemplo

El código de este proyecto de ejemplo está instalado de forma predeterminada en
C:\Archivos de Programa\Lanapsoft\BotDetect\ASP\v2.0\Samples\CaptchaAjaxValidation\

También es posible accesar a esto desde el menú inicio:
Programas > Lanapsoft > BotDetect > ASP > v2.0 > Samples > CAPTCHA Ajax Validation Sample

BotDetectAjaxDemo.asp

Código fuente completo

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head>
    <title>BotDetect CAPTCHA ASP Ajax Demo - Input Page</title>
    <link type='text/css' rel='Stylesheet' href='FormStyle.css' />
    <script type="text/javascript" src="BotDetectScript.js"></script>
    <script type="text/javascript" src="BotDetectAjaxValidation.js">
    </script>
    <script type="text/javascript" src="jquery-1.2.3.pack.js">
    </script>
  </head>
  <body>
    <form name="SampleForm" id="SampleForm" method="post" 
      action="ProcessFormAjax.asp">
    <fieldset id="SampleFields">
      <legend>Sample input form</legend>
      <div class="input">
        <label for="FirstName">First Name:</label>
        <input name="FirstName" id="FirstName" type="text" 
          class="textbox" value="<%=Request("FirstName") %>" />
      </div>
      <div class="input">
        <label for="LastName">Last Name:</label>
        <input name="LastName" type="text" id="LastName" 
        class="textbox" value="<%= Request("LastName") %>" />
      </div>
    </fieldset>
    <fieldset id="CaptchaValidation">
      <legend>CAPTCHA Validation</legend>
      <div id="PromptDiv">Retype the code from the picture</div>
      <div id="CaptchaDiv">
        <div id="CaptchaImage">
          <img id="SampleForm_CaptchaImage" src="
            LanapBotDetectHandler.asp?Command=CreateImage&
            TextStyle=4&ImageWidth=238&ImageHeight=50&
            CodeLength=5&CodeType=0" alt='CAPTCHA Code Image' />
        </div>
        <div id="CaptchaIcons">
          <a href='LanapBotDetectHandler.asp?Command=CreateSound' 
            onclick='LBD_LoadSound("SampleForm_SoundPlaceholder", 
            "LanapBotDetectHandler.asp?Command=CreateSound");
            return false;' title="Speak the code"><img src="
            speaker.gif" alt="Speak the code" /></a>
          <a href='#' onclick='LBD_ReloadImage(
            "SampleForm_CaptchaImage"); return false;' 
            title="Change the code"><img src="reload.gif" 
            alt="Cambiar el código" /></a>
          <div id='SampleForm_SoundPlaceholder' class="placeholder">
            &nbsp;</div>
        </div>
      </div>
      <div class="input">
        <label for="CaptchaCode">Code:</label>
        <input name="CaptchaCode" id="CaptchaCode" type="text" 
          class="textbox" onkeyup="this.value = 
          this.value.toLowerCase();" />
      </div>
      <%
        If Request("WrongCode") <> "" then
          Response.Write("<div><span id='CodeIncorrectLabel'>
            Incorrect code!</span></div>")
        Else
          Response.Write("<div><span id='CodeIncorrectLabel' 
            style='visibility:hidden;'>Incorrect code!</span></div>")
        End if 
      %>
      </fieldset>
      <div id="ActionDiv">
        <input type="submit" name="ProcessForm" value="Process Form" 
          id="ProcessForm" onclick="return LBD_Validate();" />
      </div>
      <div id="Note">
        <span>NOTE: the Trial version will use "LANAP" instead of a 
        random code in 50% of CAPTCHA images.</span>
      </div>
    </form>
  </body>
</html>

Explicación

El código de validación Ajax que usa BotDetect CAPTCHA existe en el archivo BotDetectAjaxValidation.js. Como usamos jQuery para las consultas Ajax, debemos incluir ambos scripts. Para actualizar a nuevas versiones de jQuery, sólo debe referenciarse a la nueva versión. El código que respecta a la imagen y sonido del CAPTCHA no necesitan modificaciones en este caso, relacionándolas con la validación del código CAPTCHA por medio de Ajax.

La validación actual del CAPTCHA ocurre con una llamada asincrónica ejecutada por onclick. Una forma posible es procesar el formulario en el evento onsubmit, pudiendo también verificar otros campos ingresados de su formulario.

ProcessFormAjax.asp

Código fuente completo

<%
  'Captcha validation
  Dim result, codeKey, inputCode
  result = False
  codeKey = "LanapBotDetectCode"
  inputCode = Request("CaptchaCode")

  If (Session(codeKey)<>"") Then
    code = Session(codeKey)
    result = (0 = StrComp(inputCode, code, 1))
    'each Captcha code can only be validated once
    Session(codeKey) = ""
  End If

  If result = False Then
    first_name = Request("FirstName")
    last_name = Request("LastName")
    redirect_url = "BotDetectAjaxDemo.asp?FirstName=" + _ 
      first_name + "&LastName=" + last_name + "&WrongCode=WrongCode"
    Response.Redirect redirect_url
  End If
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
  <title>BotDetect CAPTCHA ASP Ajax Demo - Protected Page</title>
  <link type='text/css' rel='Stylesheet' href='FormStyle.css' />
</head>
<body>
  <form name="form1" method="post" id="form1" 
    action="ProcessForm.asp">
    <fieldset id="Properties">
      <legend>BotDetect CAPTCHA Validación passed!</legend>
      <div class="input">
        <label for="FirstName">First Name:</label>
        <input name="FirstName" id="FirstName" type="text" 
          class="textbox" readonly="readonly" 
          value="<% =Request("FirstName") %>" />
      </div>
      <div class="input">
        <label for="LastName">Last Name:</label>
        <input name="LastName" id="LastName" type="text" 
        class="textbox" readonly="readonly" 
        value="<% =Request("LastName") %>" />
      </div>
    </fieldset>
    <div id="ActionDiv">
      <a href="BotDetectAjaxDemo.asp">Back to the sample form</a>
    </div>
  </form>
</body>
</html>

Explicación

Elementos de la validación del código CAPTCHA del lado del servidor no necesitan cambios al usar esta validación del lado del cliente con Ajax, las mismas explicaciones usadas en el ejemplo de validación de CAPTCHA pueden ser utilizadas acá. Básicamente, como las operaciones de validación del lado del cliente es solo un elemento que mejora la usabilidad del formulario, no involucra una validación real del código (al ser insegura y un flanco de posible falla, tal como se explica en la siguiente pregunta de nuestro apartado de preguntas frecuentes), siempre validaremos el código de forma tradicional en el servidor.

LanapBotDetectHandler.asp

Código fuente completo

<%
Dim code, codeKey, codeHash, codeHashKey, captchaId, comCaptcha

'the Captcha code is kept in Session state with this key
codeKey = "LanapBotDetectCode"
codeHashKey = "LanapBotDetectCodeHash"

'if there are multiple Captchas on tn the site, a Captcha id 
'is required to distinguish between them; otherwise, it can 
'be ignored
captchaId = Request("CaptchaId")
If(captchaId<>"") Then
  codeKey = codeKey & "_" & captchaId
End If

If (Request("Command")="CreateImage") Then
'Captcha image generation

  'create the Captcha component instance
  Set comCaptcha = CreateObject("Lanap.BotDetect")

  'process Captcha properties
  If (Request("TextStyle")<>"") Then 'set Captcha algorithm
    On Error Resume Next
    comCaptcha.TextStyle = CLng(Request("TextStyle"))
    Err.Clear
  End If
  If (Request("ImageWidth")<>"") Then 'set Captcha image width
    On Error Resume Next
    comCaptcha.ImageWidth = CLng(Request("ImageWidth"))
    Err.Clear
  End If
  If (Request("ImageHeight")<>"") Then 'set Captcha image height
    On Error Resume Next
    comCaptcha.ImageHeight = CLng(Request("ImageHeight"))
    Err.Clear
  End If
  If (Request("CodeLength")<>"") Then 'set Captcha code length
    On Error Resume Next
    comCaptcha.CodeLength = CLng(Request("CodeLength"))
    Err.Clear
  End If
  If (Request("CodeType")<>"") Then 'set Captcha code type
    On Error Resume Next
    comCaptcha.CodeType = CLng(Request("CodeType"))
    Err.Clear
  End If
  If (Request("Format")<>"") Then 'set Captcha image format
    On Error Resume Next
    comCaptcha.Format = Request("Format")
    Err.Clear
  End If

  'set Captcha image Http response headers
  Response.Buffer = True
  Response.CacheControl = "no-cache, no-store, must-revalidate"
  Response.AddHeader "Pragma", "no-cache"
  Response.Expires = -1
  If (comCaptcha.Format="JPEG") Then
    Response.ContentType = "image/jpeg"
  ElseIf (comCaptcha.Format="PNG") Then
    Response.ContentType = "image/png"
  ElseIf (comCaptcha.Format="GIF") Then
    Response.ContentType = "image/gif"
  ElseIf (comCaptcha.Format="BMP") Then
    Response.ContentType = "image/bmp"
  End If

  'generate the Captcha image binary data
  Dim varPicture
  varPicture = comCaptcha.CreateImage

  'save the Captcha code for sound generation and validation
  code = comCaptcha.GetValue
  Session(codeKey) = code
  'save the code hash for backward compatibility with older 
  'validation code
  codeHash = comCaptcha.GetHashValue
  Session(codeHashKey) = codeHash

  'send Captcha image binary data to the client
  Response.BinaryWrite varPicture
  Set comCaptcha = Nothing 'dispose of the Captcha component instance
  Response.End
'end Captcha image generation

ElseIf (Request("Command")="CreateSound") Then
'audio Captcha generation

  'create the Captcha component instance
  Set comCaptcha = CreateObject("Lanap.BotDetect")

  'set Http response headers
  If (Request.ServerVariables("HTTPS")="off") Then
    Response.CacheControl = "no-cache"
    Response.AddHeader "Pragma", "no-cache"
    Response.Expires = -1
  End If
  Response.Buffer = True
  Response.ContentType = "audio/x-wav"
  Response.AddHeader "content-disposition", _
    "attachment; filename=captcha.wav"
  Response.AddHeader "Content-Transfer-Encoding", "binary"
  Response.AddHeader "Connection", "Close"

  'generate the audio Captcha binary data from the saved code
  code = Session(codeKey)
  varSound = comCaptcha.CreateSoundFromCode(code)

  'send audio Captcha binary data to the client
  Response.BinaryWrite varSound
  Set comCaptcha = Nothing 'dispose of the Captcha component instance
  Response.End
'end audio Captcha generation

ElseIf (Request("Command")="Validate") Then
'Ajax Captcha validation

  Dim result
  result = False

  If (Session(codeKey)<>"") Then
    Dim inputCode
    inputCode = Request("Code")
    code = Session(codeKey)
    result = (0 = StrComp(inputCode, code, 1))
    'Ajax validation shouldn't remove the code if successful, so both 
    'client- and server-side validation can be performed and pass
    If (Not result) Then
      Session(codeKey) = ""
    End If
  End If

  'Http response headers
  Response.Buffer = True
  Response.ContentType = "text/javascript"
  Response.CacheControl = "no-cache, no-store, must-revalidate"
  Response.AddHeader "Pragma", "no-cache"
  Response.Expires = -1
  Response.AddHeader "Connection", "Close"

  'DEBUG: used to test Ajax request timeout handling
  'Dim objShell
  'Set objShell = CreateObject("WScript.Shell")
  'objShell.Popup "", 7, ""
	
  'send the JSON validation result to the client
  Response.Write "{ 'result': " & LCase(CStr(result)) & " }"
  Response.End

'end Ajax Captcha validation
End If

'If neither of the above conditions was met
Response.Status = "400 Bad Request"
Response.End
%>

Explicación

La interfaz Http usada para la validación del CAPTCHA usando Ajax se incluye en el archivo LanapBotDetectHandler.asp por defecto. Es accesada por una consulta similar a LanapBotDetectHandler.asp?Command=Validate&Code=ABCDE, y retorna un resultado simple en formato JSON ( { 'result': true } o { 'result': false } ).

La mayor diferencia entre la validación utilizando Ajax, es que cuendo el usuario ingresa el código correcto, el mismo código no será eliminado de la variables de estado. De esta manera, el mismo código puede ser validado varias veces (primero desde una rutina del lado del cliente por medio de Ajax y luego desde el servidor con la validación final). De todas formas, el código es eliminado luego de la validación desde el lado del servidor.

Cuando la validación asincrónica del CAPTCHA falla, nosotros borramos o cambiamos el código CAPTCHA. De otra forma, robots podrían intentar validar la misma imagen CAPTCHA asociado al código repetidamente. Esto sería una clara posible falla de seguridad.

BotDetectScript.js

Código fuente completo

function LBD_LoadSound(soundPlaceholderId, soundLink) {
  if(document.getElementById) {
    var i = soundLink.indexOf('&d=');
    if (-1 != i) {
      soundLink = soundLink.substring(0, i);
    }
    soundLink = soundLink + '&d=' + LBD_GetTimestamp();

    var placeholder = document.getElementById(soundPlaceholderId);
    var objectSrc = "<object id='captchaSound' 
      classid='clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95' 
      height='0' width='0' style='width:0; height:0;'><param 
      name='AutoStart' value='1' /><param name='Volume' value='0' 
      /><param name='PlayCount' value='1' /><param name='FileName' 
      value='" + soundLink + "' /><embed id='captchaSoundEmbed' 
      src='"+ soundLink + "' autoplay='true' hidden='true' 
      volume='100' type='"+ LBD_GetMimeType() +"' 
      style='display:inline;' /></object>";

    placeholder.innerHTML = "";
    placeholder.innerHTML = objectSrc;
  }
}

function LBD_GetTimestamp() {
  var d = new Date();
  var t = d.getTime() + (d.getTimezoneOffset() * 60000);
  return t;
}

function LBD_GetMimeType() {
  var mimeType = "audio/x-wav";
  return mimeType;
}

var LBD_ImgId = null;
var LBD_Img = null;
var LBD_NewImg = null;
var LBD_Parent = null;
var LBD_ImagePrompt = null;

function LBD_ReloadImage(imgId) {
  if(imgId) {
    LBD_ImgId = imgId;
    LBD_Img = document.getElementById(LBD_ImgId);
    var src = LBD_Img.src;

    var i = src.indexOf('&d=');
    if (-1 != i) {
      src = src.substring(0, i);
    }
    var newSrc = src + '&d=' + LBD_GetTimestamp();

    LBD_NewImg = document.createElement('img');
    LBD_NewImg.onload = LBD_ShowImage;
    LBD_NewImg.id = LBD_Img.id;
    LBD_NewImg.alt = LBD_Img.alt;
    LBD_NewImg.src = newSrc;

    LBD_ImagePrompt = document.createElement('span');
    LBD_ImagePrompt.appendChild(document.createTextNode('loading...'));

    LBD_Parent = LBD_Img.parentNode;
    LBD_Parent.removeChild(LBD_Img);
    LBD_Parent.appendChild(LBD_ImagePrompt);
  }
}

function LBD_ShowImage() {
  if(LBD_NewImg && LBD_Parent && LBD_ImagePrompt) {
    LBD_Parent.removeChild(LBD_ImagePrompt);
    LBD_Parent.appendChild(LBD_NewImg);
  }
}

Explicación

Este es el código clásico que utilizamos constantemente con BotDetect, para solicitudes del sonido y el refresco de imágenes CAPTCHA, que también es usado para la validación con Ajax del código, sin necesitar cambios para instancias de uso que no usen Ajax.

BotDetectAjaxValidation.js

Código fuente completo

// element identifiers 
var captchaImageId = 'SampleForm_CaptchaImage';
var codeTextboxId = 'CaptchaCode';
var errorLabelId = 'CodeIncorrectLabel';
var formId = 'SampleForm';

// other settings
var validationPrompt = 'validating...';
var ajaxTimeoutMiliseconds = 5000;

// shared variable declarations
var LBD_ValidationResult = false;
var LBD_ValidationRequest = null;
var LBD_CodeInput = null;
var LBD_ValidationPrompt = null;
var LBD_ErrorPrompt = null;
var LBD_PromptParent = null;

function LBD_Validate() 
{
  if(!LBD_ValidationResult) 
  { // only validate the CAPTCHA if it isn't already passed
  
    // init elements
    LBD_CodeInput = document.getElementById(codeTextboxId);
    LBD_ErrorPrompt = document.getElementById(errorLabelId);
    LBD_PromptParent = LBD_ErrorPrompt.parentNode;
    
    if(!LBD_CodeInput || !LBD_CodeInput.value || 
      LBD_CodeInput.value.length < 0) 
    { // validation fails if there is no user input
      LBD_EndValidation(false);
      LBD_CodeInput.focus();
    } 
    else 
    { // Validación Ajax
      LBD_StartValidation();
    }
  }
  return LBD_ValidationResult;
}

function LBD_StartValidation() 
{
  // hide the error message
  LBD_ErrorPrompt.style.visibility = 'hidden';
  
  // show the validation status indicator
  LBD_ValidationPrompt = document.createElement('span');
  LBD_ValidationPrompt.id = 'LBD_ValidatingPrompt';
  LBD_ValidationPrompt.appendChild(document.createTextNode(
    validationPrompt));
  LBD_PromptParent.appendChild(LBD_ValidationPrompt);

  // send the user input to the server for validation
  LBD_ValidateCode(LBD_CodeInput.value, LBD_EndValidation);
}

function LBD_ValidateCode(code, callback) 
{
  // set Ajax request timeout treshold
  $.ajaxSetup( { 
    timeout : ajaxTimeoutMiliseconds
  } );
  
  // send CAPTCHA Validación request
  LBD_ValidationRequest = $.getJSON( 
    "LanapBotDetectHandler.asp", // server path
    { Command: "Validate", Code: code }, // querystring params
    function(json) { 
      LBD_ProcessValidationResult(json.result, callback) 
    } // callback function
  );
  
  // if Ajax CAPTCHA Validación timeouts, fall back to full 
  // form postback
  $("#" + formId).ajaxError(function(LBD_ValidationRequest, 
    settings, err) { 
    LBD_PromptParent.removeChild(LBD_ValidationPrompt);
    this.submit(); 
  });
}

function LBD_ProcessValidationResult(result, callback) 
{ 
  // when the validation result arrives, remove validation status 
  // indicator
  LBD_PromptParent.removeChild(LBD_ValidationPrompt);

  if(!result) 
  { // CAPTCHA Validación failed, reset the CAPTCHA
    LBD_ReloadImage(captchaImageId);
    LBD_CodeInput.value = '';
  }

  if ("function" == typeof(callback)) 
  { // invoke the registered callback function
    callback(result);
  }
}

function LBD_EndValidation(result) 
{
  if(result) 
  { // CAPTCHA Validación passed, submit the form to the server
    LBD_ErrorPrompt.style.visibility = 'hidden';
    LBD_ValidationResult = true;
    document.getElementById(formId).submit();
  } 
  else
  { // CAPTCHA Validación failed, show error message
    LBD_ErrorPrompt.style.visibility = 'visible';
  }
}

Explicación

El propósito principal de utilizar una validación de CAPTCHA por Ajax es sólo para proporcionar una mejor experiencia de usuario, sin afectar el resto la página. La validación del CAPTCHA continúa funcionando normalmente pero el usuario tiene mejor experiencia de uso sin comprometer la seguridad el sitio contra los robots.

Estas mejoras de la experiencia del usuario tiene un pequeño precio en cuanto a rendimiento pues se requieren validar dos veces el CAPTCHA incluso cuando es ingresado correctamente, necesitando más veces cuando es incorrectamente escrito. Sin embargo escribir un código incorrectamente tiene una mejor experiencia para el usuario pues no tiene que perder lo escrito en un formulario y la respuesta es instantánea (suavizando su experiencia).

En el código todos los identificadores de la validación Ajax del CAPTCHA están definidos al comienzo del archivo para que usted pueda modificarlo fácilmente.
En la validación del lado del cliente siempre se verifica que el CAPTCHA tenga escrito un código para no hacer llamadas inútiles al servidor. Como está definido en la función LBD_EndValidation, cuando la validación del CAPTCHA falla, se muestra un mensaje de error, de otra forma, el mensaje de error permanece oculto y el formulario se envía para ser validado al servidor.

Cómo la validación del CATCHA se ejecuta de manera asincrónica, las funciones de validación son diferentes para las llamadas ajax y la revisión del servidor. La consulta jQuery está en negritas para ser destacada. Sólo hay un manejo de errores básico en este ejemplo, y aunque puede efectuarse un manejo de problemas mucho más detallado, en este caso es suficiente.

FormStyle.css

Código fuente completo

body {
  background-color: #EEEEFF;
  font-family: Verdana, Arial;
  font-size: 0.9em;
}

fieldset {
  padding: 0 10px 10px 10px;
  margin: 11px;
  width: 300px;
  display: block;
}

div.input {
  margin: 7px 0;
}

legend {
  padding: 5px;
  color: #999999;
}

label {
  display: block;
  width: 85px;
  float: left;
  text-align: right;
  padding-right: 5px;
}

input.textbox {
  width: 170px;
}

input.textboxSmall {
  width: 40px;
}

#CodeIncorrectLabel {
  color: Red;
}

#CodeCorrectLabel {
  color: Green;
}

#Note {
  padding: 0;
  margin: 11px;
  margin-bottom: -7px;
  width: 320px;
  font-size: 0.8em;
  color: Red;
}

#PromptDiv {
  padding: 0;
  margin: 0;
  margin-bottom: 8px;
}

#ActionDiv {
  padding: 0 0 10px 10px;
  margin: 11px;
  margin-right: 0;
  width: 314px;
  text-align:right;
}

fieldset #ActionDiv{
  padding: 0;
  margin: 0;
  width: auto;
  text-align:right;
}

#CaptchaDiv {
  margin: 0;
  padding: 0;
  width:265px;
  height:50px;
  padding-bottom: 5px;
}

#CaptchaImage {
  float: left;
  margin: 0;
  padding: 0;
  width:240px;
  height:50px;
}

#CaptchaIcons {
  width: 22px;
  height: 50px;
  float: right;
  text-align: left;
  margin: 0;
  padding: 0;
}

td#CaptchaIcons {
  padding-left: 3px;
}

#CaptchaIcons img {
  border: 0;
  margin: 0;
  padding: 0;
  padding-bottom: 3px;
}

*html #CaptchaIcons img {
  margin-bottom: -2px;
}

.placeholder {
  visibility: hidden;
  width:0 !important;
  height:0 !important;
}

*html .placeholder {
  display: none !important;
}

#CaptchaPreviewDiv {
  margin: 0;
  padding: 0;
  padding-bottom: 5px;
}

div.FeaturesInput {
  margin: 7px 0;
}

div.FeaturesInput label {
  width: 110px;
}

Explicación

Los estilos definidos acá son los mismos que los definidos en el ejemplo de la validación de una CAPTCHA.

Versiones Actuales de BotDetect

Advertencia

Esta página es una traducción no oficial de la página original: BotDetect CAPTCHA Ajax Validation ASP Code Sample y puede estar incompleta, incorrecta o poco actualizada.

Última traducción del 2009-12-18. Esto se aplica para los productos BotDetect ASP.NET CAPTCHA v2.0.15 y BotDetect ASP CAPTCHA v2.0.9.

language: English Español Tiếng Việt