Salve, salve Delphianos!

Neste pequeno artigo demonstrarei algumas técnicas para criação de Sessions para aplicações baseadas em WebBroker.

Mas para que servem as Sessions? Uma resposta curta e grossa: serializar usuários em aplicações Web! Xiiiiiii Facunte, agora piorou, explique melhor!?

Vejamos dois exemplos:

e-Commerce

Serializamos clientes através de Sessions, onde todas as ações devem ser controladas, como adição de produtos no carrinho de compras, processo de identificação e pagamento.

Veja o esquema:

  • Internauta entra na loja virtual e recebe uma sessão única: A01-FF1;
  • Internauta escolhe produto e coloca na sua cesta. Sua cesta possui a mesma identificação da sessão: A01-FF1;
  • Internauta entra na área de identificação e o sistema vincula seu cadastro à sessão: A01-FF1;
  • Internauta finaliza processo de compra, e o sistema identifica suas opções através da sessão: A01-FF1.

iBanking

Utilizada no processo de identificação do cliente, bem como em todas as ações realizadas durante a sessão (transferência entre contas, pagamentos, extrato, etc).

  • Cliente entra no site do banco e recebe uma sessão única: BBC-AAX;
  • Cliente informa dados de sua conta e realiza login do iBanking. O sistema vincula a conta logada na sessão BBC-AAX. Para maior segurança, além de um servidor seguro, a sessão criada possui algumas informações do internauta, como por exemplo, o número do IP (obviamente criptografado);
  • Cliente realiza todas as transações de maneira segura, onde o sistema sempre verifica as informações da sessão BBC-AAX, e da máquina do usuário;
  • Cliente finaliza processo, e o sistema libera a sessão BBC-AAX.

Mas chega de apresentações! Chegou a hora de por a mão na massa.

Listagem 1: Função randômica para criação de sessões

function NewIDRamdom:String;
begin
Result:= IntToHex(Random($ffffffff),8)+'-'+
   IntToHex(Random($ffffffff),8)+'-'+
               IntToHex(Random($ffffffff),8)+'-'+
               IntToHex(Random($ffffffff),8);
end;

Listagem 2: Função que utiliza diversos dados, como data e hora

function NewIDPlus:String;
 var
 iAno, iMes, iDia, iHora, iMinuto, iSegundo, iMSegundo: Word;
 begin
    DecodeDate(Now, iAno, iMes, iDia);
    DecodeTime(Now, iHor, iMin, iSeg, iMSeg);
    Result:= IntToHex(iAno,3)+'-'+
                IntToHex(iMes,2)+'-'+
                IntToHex(iDia,2)+'-'+
                IntToHex(iMSegundo,3)+'-'+
                IntToHex(iSegundo,2)+'-'+
                IntToHex(iMinuto,2)+'-'+
                IntToHex(iHora,2);
 end;

Listagem 3: Função que retorna uma GUID

function NewIdGuid:String;
var
     MinhaGUID:TGUID;
begin
     CoCreateGuid(MinhaGuid);
     Result:=GUIDToString(MinhaGuid);
end;

Nesse exemplo, necessitamos de duas units: ActiveX e COMOBJ.

 Exemplos de utilização:

Variável de sessão:

var
 IDSession: String;

Variável Internet, que identifica o usuário:

<input type=”hidden” name=”ID” value=”...>
 http://servidor/aplicacao.dll/login?ID=....

No evento OnBeforeDispatch insira o código que segue:

begin
  if Request.ContentFields.values[‘ID’]=’’ then
     IDSession := NewIdGuid;
end;

 E no parser do seu Producer (onHTMLTag), coloque o código que segue:

begin
    if TagString=’ID’ then
      ReplaceText:=IDSession;
end;

 Em todas as páginas você deverá inserir a Tag transparente <#ID>, exemplo:

<input type=”hidden” name=”ID” value=”<#ID>”>
 http://servidor/aplicacao.dll/login?ID=<#ID>
 <form name=”cliente” action=”/apl.dll/confirma?ID=<#ID>”>

A seguir demonstrarei como criar e administrar sessions para o saudoso WebBroker. Crie uma nova aplicação baseada na tecnologia WebBroker, através das opções File/New.../WebServer Application. Selecione qualquer uma das opções (CGI, ISAPI, Apache...). Salve o projeto com o nome Sessoes.DPR e a unit como un_sessoes.pas. Com o objetivo de criar um projeto profissional, estaremos utilizando um banco de dados para armazenar informações sobre as sessions.

 

Veja a estrutura das tabelas:

Tabela de Sessão
Campo Tipo
SSS_ID Varchar PK
SSS_USU_Id Int
SSS_DataHoraUltAtualizacao DateTime
SSS_IP Varchar
 
Tabela Usuário
Campo Tipo
USU_ID Int PK
USU_Nome Varchar
USU_Senha Varchar

A estrutura modelo é baseada em SQL Server, mas você poderá adaptar facilmente em qualquer SGDB. Em nosso projeto utilizaremos ADO, mas você poderá utilizar a tecnologia dbExpress, ou até mesmo o BDE. Prosseguindo com o projeto, insira os seguintes objetos.

AdoConnection
Name dbconexao
LoginPrompt False
ConnectionString Faça a conexão com a sua base de dados.
AdoDataSet
Name QrySessao
Connection dbConexao

Neste ponto implementaremos as rotinas comuns para o tratamento de sessions. Na seção public, declare a função NewIdGuid  (responsável pela criação de um novo ID), e faça a implementação como segue:

function NewIdGuid:String;
var
     MinhaGUID:TGUID;
     sIDInterno:String;
begin
     CoCreateGuid(MinhaGuid);
     sIDInterno:= GUIDToString(MinhaGuid);
     // Adiciona a sessão no banco de dados
     dbConexao.Execute(‘Insert into Sessao values(‘+
        QuotedStr(sIDInterno)+’,’+
        ‘0,’+
        QuotedStr(DateTimetoStr(Now))+’,’+
        QuotedStr(Request.RemoteAddr)+’)’,      
         cmdText, [eoExecuteNoRecords]);    
     Result:= sIDInterno;
end;

Vamos analisar o código:

 

Criamos  uma chave única (ID)  e setamos para sIDInterno.      CoCreateGuid(MinhaGuid);

sIDInterno:= GUIDToString(MinhaGuid);

Inserimos o novo ID no banco de dados, e transmitimos os seguintes parâmetros:

    
dbConexao.Execute(‘Insert into Sessao values(‘+
 novo ID,      QuotedStr(sIDInterno)+’,’+
 

Usuário = 0. Isso indica que ainda não efetuou login.    

‘0,’+
 

Data e Hora Atual.

QuotedStr(DateTimetoStr(Now))+’,’+

E o IP do usuário.

     
QuotedStr(Request.RemoteAddr)+’)’,
        

Com isso apenas adicionamos a chave no banco de dados. Na cláusula uses, acrescente as units ComObj e ActiveX.

Na seção protected,  declare a seguinte variável:

protected
     sID: String;

A variável sID será responsável pelo controle do session ID. Neste ponto criaremos a função que irá checar a existência de um ID e retornar o valor do mesmo.

function ChecaId:String;
var
   sIDinterno:string;
begin
     // verifica se existe ID (post)
     sIDInterno:=Request.ContentFields.Values[‘ID’];
     if sIDInterno=’’  then
     begin
          // verifica se existe ID (get)
          sIDInterno:=Request.QueryFields.Values[‘ID’];
          if sIDInterno=’’ then
          begin
              // não existe ID, cria um novo;
             sIDInterno:=NewIdGuid;
          end;
     end;
     // Retorna ID
     Result:=sIDInterno;
end;
 

Vamos analisar a lógica da função:

Neste ponto verificamos a existência do campo ID num FORM com método POST.

// verifica se existe ID (post)
sIDInterno:=Request.ContentFields.Values[‘ID’];
if sIDInterno=’’  then
begin

Em caso negativo, verifica-se a existência do campo ID num método GET, e se persistir, criamos um novo ID.

Este é ponto mais importante da sua aplicação. Necessitamos transmitir o campo ID em todas as ações, seja dentro de um form ou através de uma URL, exemplos:

   
1. http://servidor/aplicacao.dll?ID=<#ID>
2. <form method=post action=”aplicação.dll”>
   <input type=hidden name=”ID” value=”<#ID>”>

Repare que em ambos os casos utilizamos a tag transparente . Com isso deveremos realizar o parser em todos os objetos  Producers através do evento OnHtmlTag;

Amigos, em todos os producers (normalmente centralizo minhas aplicações num único Producer), vocês deverão realizar o parser. Veja o exemplo:

Evento OnHtmlTag.

If TagString=’ID’ then
begin
  ReplaceText:=ChecaId;
end;
 

Neste ponto criaremos as funções para efetuar LogIn e LogOut além de uma função que verifica se o usuário está logado no sistema. Como exemplo, crie um HTML com os campos: usuario e senha:

 
<html>
<body>
<form method=”post” action=”login”>
    <input type=”HIDDEN” name=”ID” value=”<#ID>”
    Usuario <input type=TEXT name=usuario><BR>
    Senha <input type=password name=senha><BR>
    <input type=submit value=”confirma”>
</form>
</body>
</html>
 

A função Login retorna uma expressão lógica com o resultado da operação.

 
function Login:boolean;
var
   sUsuario, sSenha, sURL:string;
begin
  sID:=ChecaId;
  sUsuario:=Request.ContentFields.Values[‘usuario’];
  sSenha:=Request.ContentFields.Values[‘senha’];
  with QrySessao do
  begin
   Close;
   CommandText:=’Select usu_id, usu_nome, usu_senha from usuario whereUSU_NOME=’+QuotedStr(sUsuario);
   Open;
   If Empty or FieldByName(‘usu_senha’).AsString<>sSenha then
   begin
     Result:=False;
   end
   Else
   begin
     // Atualiza
      dbConexao.Execute(‘UpDate Sessao set ‘+          ‘SSS_Usu_id=’+FieldByName(‘usu_id’).AsString+’, ‘+    ‘SSS_DataUltAtualizacao=’+QuotedStr(DateTimetoStr(Now))+
    ‘,’+ QuotedStr(Request.RemoteAddr)+’)’,      
    cmdText, [eoExecuteNoRecords]);         
     Result:=True;
   End;
   Close;
   end;   
end;
 

A função Login, verifica a existência do usuário e a validade da senha. Em caso positivo, a tabela Sessao é atualizada com os dados do usuário(ID, Código do Usuário e Data/Hora atualização). Com isso autenticamos o usuário em nossa base.Além disso utilizamos a variável sURL que contém a URL de destino após o usuário efetuar o Login no sistema. Exemplo:

- Usuário tenta acessar uma área restrita

 

Função LogOut

A função LogOut retira o vínculo do usuário na tabela Sessão. Devemos passar o parâmetro ID para que a operação seja realizadda.

function Logout(ID:String):boolean;
begin
    try
       dbConexao.Execute(‘Update Sessao set SSS_Usu_id=0 where SSS_ID = ‘+ID,cmdText,        [eoExecuteNoRecords]);
           Result:=True;         
       except
            Result:=False;
       end;
end;

Função VerificaLogin

Esta função verifica se o usuário está logado no sistema através da autenticação e do tempo. Estou prevendo 1 dia, mas você poderá configurar de acordo com a sua necessidade (1 hora, 30 minutos, 10 minutos, etc).

function VerificaLogin(ID):boolean;
begin
  with QrySessao do
  begin
   Close;
   CommandText:=’select SSS_USU_ID, SSS_DataHoraUltAtualizacao from Sessao where SSS_ID=’+ID;
   Open;
   If (Empty) or
      (FieldByName(‘SSS_USU_ID’).AsInteger=0) or
      (FieldByName(‘SSS_DataHoraUltAtualizacao’).Value<Now-1
    then
     Result:=False
    Else
     Result:=True;
  end;
end;

Utilizando as funções

Em todas as ações que necessitam de Login, você deverá inserir o código abaixo:

 
if not(VerificaLogin(sID)) then
begin
  // coloque aqui o código que chama a sua tela de LOGIN
  // exemplo:
  Response.Content:=’’;
end;
 

Sugiro a criação de uma rotina de expurgo para eliminar todos os registros da tabela Sessao com atualização inferior a 2 dias.

Espero que tenham compreendido o artigo e que possam aproveitá-lo em seus sistemas.

 

Forte abraço.