Publicidade:

quarta-feira, 25 de fevereiro de 2015

Arduino - Autenticação HTTP de Usuário e Senha - Ethernet Shield

Por mais simples que uma aplicação com Ethernet Shield seja, ter um login pra autenticar o usuário pode ser uma boa. Mas nem sempre pode-se contar com uma base de dados de usuário ou algo mais elaborado.


Uma solução simples, é utilizar o próprio browser pra solicitar usuário e senha.



código fonte:



/*
 Web Server - HTTP Autentication
 Baseado na versão de exemplo do Arduino.
 
 ATENÇÃO: esse código é apenas para exemplo e não deve ser encarado como solução 
 completa para segurança de informações de qualquer aplicação.
 
 modified 25 feb 2015
 by Fabiano A. Arndt (fabianoallex)
*/
/*
 descrição Original:
 
 Web Server

 A simple web server that shows the value of the analog input pins.
 using an Arduino Wiznet Ethernet shield.

 Circuit:
 * Ethernet shield attached to pins 10, 11, 12, 13
 * Analog inputs attached to pins A0 through A5 (optional)

 created 18 Dec 2009
 by David A. Mellis
 
 modified 9 Apr 2012
 by Tom Igoe
 
*/

/**********************************************************************************
************************************BIBLIOTECAS************************************
**********************************************************************************/

#include <SPI.h>
#include <Ethernet.h>

/**********************************************************************************
********************************FIM BIBLIOTECAS************************************
**********************************************************************************/

/**********************************************************************************
*************************ROTINAS USUARIO E SENHA***********************************
***********************************************************************************/

boolean validar_usuario(char * linebuf) {
  /*
  nesse exemplo o usuario e senha estão definidos dentro do código fonte.
  mas o usuário e senha poderiam ser autenticados de diversas maneiras,
  lendo os dados de um servidor web, arquivo texto, etc, bastando apenas atribuir
  o valor lido para a variável usuario_senha. 
  */
  
  char usuario_senha[] = "admin:admin";
  int t = strlen(usuario_senha);
  
  int tamanhoEnc = (((t-1) / 3) + 1) * 4;   //tamanho da string codificada
  char out[tamanhoEnc];
  base64_encode(out, usuario_senha, t+1 );
  
  //---desconta é usado pra eliminar os caracteres '='
  int desconta = 0;
  if ((t%3) == 1) { desconta = 2; }
  if ((t%3) == 2) { desconta = 1; }
  
  char out2[tamanhoEnc-desconta];
  
  byte i;
  for (i=0; i<(tamanhoEnc-desconta);i++){ out2[i] = out[i]; }
  out2[i] = '\0';
  
  return ( strstr(linebuf, out2)>0 );
}

/**********************************************************************************
*************************FIM ROTINA USUARIO E SENHA********************************
***********************************************************************************/

/**********************************************************************************
***********************************PAGINAS HTML************************************
***********************************************************************************/
EthernetServer * server;

void iniciar_ethernet(){
  byte ip[4]      = {192,168,200,188};
  //byte gateway[4] = {192,168,1,1};
  //byte subnet[4]  = {255,255,255,0};
  byte mac[6]     = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
  int  porta      = 80;

  server = new EthernetServer(porta);

  //Ethernet.begin(mac, ip, gateway, subnet);         //caso necessario gateway utilizar essa linha
  Ethernet.begin(mac, ip);
  server->begin();
}

void html_autenticar(EthernetClient &client) {
  client.print(F("HTTP/1.1 401 Authorization Required\n"  
               "WWW-Authenticate: Basic realm=\"Area Restrita\"\n"  
               "Content-Type: text/html\n"  
               "Connnection: close\n\n"  
               "<!DOCTYPE HTML>\n"  
               "<HTML><HEAD><TITLE>Error</TITLE>\n"  
               "</HEAD><BODY><H1>401 Acesso nao autorizado</H1></BODY></HTML>\n"));
}

void html_autenticado(EthernetClient &client){
  client.println(F("HTTP/1.1 200 OK\n"
                   "Content-Type: text/html\n"
                   "Connection: keep-alive\n\n"
                   "<!DOCTYPE HTML>"
                   "<html>"));

  for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
    int sensorReading = analogRead(analogChannel);
    client.print(F("analog input "));
    client.print(analogChannel);
    client.print(F(" is "));
    client.print(sensorReading);
    client.println(F("<br>"));
  }
  client.println(F("</html>"));
}

void exec_ethernet(){
  EthernetClient client = server->available();
  if (client) {
    char linebuf[80];
    memset(linebuf, 0, sizeof(linebuf));
    
    int     charCount          = 0;
    boolean autenticado        = false;
    boolean currentLineIsBlank = true;
    boolean conteudo           = false;
    
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        
        linebuf[charCount] = c;
        
        if (charCount<sizeof(linebuf)-1) { charCount++; }
        Serial.write(c);
        
        if (c == '\n' && currentLineIsBlank) {
          autenticado ? html_autenticado(client) : html_autenticar(client);
          break;
        }
        if (c == '\n') { 
          currentLineIsBlank = true;               
          
          if (strstr(linebuf, "Authorization: Basic")>0 ) { if ( validar_usuario(linebuf) )   {  autenticado = true;  } }
          
          memset(linebuf, 0, sizeof(linebuf));
          charCount = 0;
        } else if (c != '\r') {
          currentLineIsBlank = false;    // you've gotten a character on the current line
        }
      }
    }
    
    delay(1);           // give the web browser time to receive the data
    client.stop();      // close the connection:
  }
}
/**********************************************************************************
**************************************** FIM PAGINAS HTML**************************
***********************************************************************************/


/**********************************************************************************
*************************BASE 64 CODE/DECODE DO USUARIO E SENHA********************
***********************************************************************************/

void a3_to_a4(unsigned char * a4, unsigned char * a3);
void a4_to_a3(unsigned char * a3, unsigned char * a4);
unsigned char b64_lookup(char c);

int base64_encode(char *output, char *input, int inputLen) {
  const char b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  int i=0, j=0, encLen=0;
  unsigned char a3[3], a4[4];

  while(inputLen--) {
    a3[i++] = *(input++);
    if(i == 3) {
      a3_to_a4(a4, a3);
      for(i=0; i<4; i++) { output[encLen++] = b64_alphabet[a4[i]]; }
      i = 0;
    }
  }

  if (i) {
    for(j = i; j < 3; j++)     { a3[j] = '\0';                           }
    a3_to_a4(a4, a3);
    for(j = 0; j < i + 1; j++) { output[encLen++] = b64_alphabet[a4[j]]; }
    while((i++ < 3))           { output[encLen++] = '=';                 }
  }
  output[encLen] = '\0';
  return encLen;
}

/*
int base64_decode(char * output, char * input, int inputLen) {
  int i = 0, j = 0;
  int decLen = 0;
  unsigned char a3[3];
  unsigned char a4[4];
  while (inputLen--) {
    if (*input == '=') { break; }
    a4[i++] = *(input++);
    if (i == 4) {
      for (i = 0; i <4; i++)  { a4[i] = b64_lookup(a4[i]); }
      a4_to_a3(a3,a4);
      for (i = 0; i < 3; i++) { output[decLen++] = a3[i];  }
      i = 0;
    }
  }

  if (i) {
    for (j=i; j<4; j++)   { a4[j] = '\0';             }
    for (j=0; j<4; j++)   { a4[j] = b64_lookup(a4[j]);}
    a4_to_a3(a3,a4);
    for (j=0; j<i-1; j++) { output[decLen++] = a3[j]; }
  }
  output[decLen] = '\0';
  return decLen;
}
*/

//int base64_enc_len(int plainLen) {
//  int n = plainLen;
//  return (n + 2 - ((n + 2) % 3)) / 3 * 4;
//}

//int base64_dec_len(char * input, int inputLen) {
//  int i = 0;
//  int numEq = 0;
//  for(i = inputLen - 1; input[i] == '='; i--) { numEq++; }
//  return ((6 * inputLen) / 8) - numEq;
//}

void a3_to_a4(unsigned char * a4, unsigned char * a3) {
  a4[0] = (a3[0]  & 0xfc) >> 2;
  a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
  a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
  a4[3] = (a3[2] & 0x3f);
}

//void a4_to_a3(unsigned char * a3, unsigned char * a4) {
//  a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
//  a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2);
//  a3[2] = ((a4[2] & 0x3) << 6) + a4[3];
//}

unsigned char b64_lookup(char c) {
  if(c >='A' && c <='Z') return c - 'A';
  if(c >='a' && c <='z') return c - 71;
  if(c >='0' && c <='9') return c + 4;
  if(c == '+') return 62;
  if(c == '/') return 63;
  return -1;
}

/**********************************************************************************
*************************FIM BASE 64 CODE/DECODE DO USUARIO E SENHA****************
***********************************************************************************/



/**********************************************************************************
**************************************** VOID / LOOP ******************************
***********************************************************************************/

void setup() {
  Serial.begin(9600);
  iniciar_ethernet();
}

void loop() {
  exec_ethernet();
}

/**********************************************************************************
*************************************FIM VOID / LOOP*******************************
***********************************************************************************/

17 comentários:

  1. Amigo, obrigado por disponibilizar o código. Não entendi muito a parte do encode 64. Vc está criando algum tipo de criptografia? Poderia colocar algum comentário no código?

    Obrigado mais uma vez.

    ResponderExcluir
    Respostas
    1. na verdade a informação que o usuário digita no browser, vem para o arduino criptografada, por isso, tem essas funções, pra "descriptografar"....

      Excluir
    2. Este comentário foi removido pelo autor.

      Excluir
    3. Blz. Obrigado.

      Estou tentando colocar essa página para rodar em um arquivo a parte, por exemplo, no Ethernet.cpp, para ser chamado como uma função, pois já tenho uma página rodando, na verdade em um arquivo chamado EthernetW5100.

      Qualquer coisa posto o código se eu conseguir.

      Vc sabe dizer se teve alguma alteração nas bibliotecas SPI.h e Ethernet.h?

      Obrigado mais uma vez.

      Excluir
  2. como puedo activar un relevador utilizando este codigo...........................ayuda porfavor

    ResponderExcluir
  3. BOA NOITE GOSTEI DO PROJETO , COMO POSSO IMPLEMENTAR ISSO EM UM CODIGO QUE JA POSSUO ?

    ResponderExcluir
  4. Bom dia Amigo.
    Como faço pra colocar uma opção de logoff?

    ResponderExcluir
    Respostas
    1. http://fabianoallex.blogspot.com.br/2015/03/arduino-autenticacao-http-com-logoff.html

      Excluir
  5. Vlw fabiano. Me ajudou muito. Parabéns pelo seu programa!

    ResponderExcluir
  6. esta dando esse erro "base64_encode' was not declared in this scope" como faço para corrigir

    ResponderExcluir
  7. Boa noite qual o usuário e senha? eu coloco admin em usuário e senha não entra

    ResponderExcluir
  8. Aqui também está assim , a senha não confere com a programação .. :(

    ResponderExcluir
  9. Como fazer o redirecionamento para outro pagina?

    ResponderExcluir
  10. Implementei aqui para proteção de uma página que já existia. Pois bem, funcionou em partes, no entanto se Clico no login insistentemente mesmo sem digitar a senha ele libera a página, e mesmo quando entro com a senha ele fica solicitando a mesma durante a atualização das chaves.

    ResponderExcluir
  11. Olá Fabiano estava usando esse código mas ele não funciona em compiladores da versão atual como o 1.8 por que?

    ResponderExcluir
  12. Hola Fabiano, puedes indicarme la parte donde se pide el usuario y password, esa rutina me interesa para agregarla a mi proyecto, puedes indicarme por favor?

    ResponderExcluir