Login PHP com "lembrar de mim" e bcrypt! Artigo

Conheça os cursos gratuitos do WebDevBr! - Inscreva-se!


Este artigo foi publicado a 4 anos, 2 semanas atrás.

Este artigo é muito antigo ou seu conteúdo foi descontinuado, com certeza outro artigo foi escrito para substituí-lo, use o campo de pesquisa logo acima e desculpe o incomodo!

Desta vez vou mostrar como criar um login em php com lembrar de mim e bcrypt utilizando classes, métodos e que será integrado a qualquer projeto PHP facilmente, ele poderá inclusive ser integrado ao nosso Framework PHP, vamos usar um pouco de PDO, mas não se preocupe, vou falar de tudo passo a passo.

Então vamos lá, durante este tutorial vou mostrar como se trabalhar com classes e funções (PHP OOP), cookies, seções, bcrypt e PDO, por isso vou separar em partes o artigo.

A estrutura do nosso login php

Sempre que começo a trabalhar, a primeira coisa que faço é cria todos os arquivos necessários e também o meu HTML, pra vocês vou passar tudo isso pronto e comentar o que fiz.

Gostou deste artigo?

Receba atualizações semanais com novos artigos do WebDevBr e outras dicas!

Baixe os arquivos aqui.

Na raiz do arquivo baixado temos 4 pastas e 6 arquivos, descompacte no seu servidor local. As pastas css, js e fonts são do Twitter Bootstrap 3, a pasta PHP guarda nossos arquivos de sistema, já os arquivos fora de pastas são:

  • cadastra.php - A página responsável por cadastrar os dados do usuário
  • confirma-cadastro.html - Mostra a confirmação do cadastro
  • index.html - Página inicial que da acesso as outras
  • login.php - Formulário que permite o acesso as páginas protegidas
  • logout.php - Desconecta o usuário do sistema
  • pagina-protegida.php - A página que será protegida

Como podem ver é bem simples, já temos as páginas prontas e podemos focar no principal, o sistema, e para isso criei um arquivo php chamado Usuarios.php dentro da pasta php, vamos partir para ele.

O que o Usuarios.php vai fazer?

Perguntinha cretina, mas na verdade muito útil na prática, é essa resposta que vai nos ajudar a definir o que vamos precisar criar, como já publiquei alguns artigos sobre as boas práticas de desenvolvimento vou aproveitar para segui-las, não todas, por exemplo, não vou montar o Namespace, se você, moralista de plantão, acha que falta alguma coisa, posta nos comentários sua opinião para os leitores conferirem, não quer dizer que vou alterar aqui, mas toda opinião diferente da minha é válida, isso fica para próximos artigos, então para você que está começando no php vou dar uma introdução a classes e funções e depois aplicar no nosso sistema de login.

Em PHP OOP ou PHP orientado a objetos, o uso de classes e métodos (métodos = funções) foi aprimorado na versão 5 com melhor desempenho e mais recursos, mas por enquanto vamos ficar focados no básico, classes e funções (ou métodos, eu vou usar os dois métodos).

O que são classes no php

A classe é o objeto em si, é o a "raiz do script em questão" em outras palavras a classe é o pacote que guarda todos os atributos e métodos que vamos usar na nossa aplicação, no nosso exemplo, o objeto Usuários vai nos dar acesso a todas as funções de cadastro, acesso, desconectar, e tudo mais que nosso objeto for fazer.

Como nossa classe usuários vai funcionar na prática e ajudar no login

Pra criar uma classe é super simples, basta usar o comando class antes do nome da classe, as PSR-1 e PSR-2 nos dizem que devemos escrever o comando class, em seguida o nome da classe em StudlyCaps e as chaves ("{}") de abertura e fechamento devem ficar sozinhas em uma única linha cada uma, então seguindo o que estamos fazendo, nosso Usuario.php ficaria desta forma:

class Usuarios
{
}

O que são métodos ou funções

Métodos ou funções são as ações que o nosso objeto vai executar. De acordo com as normas citadas acima, para criar uma função devemos setar a visibilidade (public, private ou protected), o comando function, o nome da função em lowerCamelCase e novamente as chaves cada uma em uma linha só pra elas, agora que vcê sabe como é vamos começar a desenvolver nosso login PHP.

Definindo o que a classe vai fazer

Neste ponto precisamos definir o que a classe Usuarios() vai fazer, e isso é bem simples, basta um pouco de organização e reflexão:

class Usuarios
{

    public function __construct()
    {
        $this->conectaBd();
    }

    public function login()
    {
    }

    public function logout()
    {
    }

    public function protege()
    {
    }

    public function cadastrar()
    {
    }

    protected function lembrar()
    {
    }

    protected function hash()
    {
    }

    protected function retUsuario()
    {
    }

    protected function conectaBd()
    {
    }

    protected function salt()
    {
    }

}

A função __construct() é a primeira a ser executada, e isso acontece de forma automática pelo PHP sem que precisemos fazer nada, por isso usei ele pra nos conectar ao banco de dados usando outro método do nosso login PHP, o conectaBd(). Veja o que as nossas outras funções fazem:

  • VISIBILIDADE PÚBLICA
  • login() - Realiza o login no sistema
  • logout() - Desconecta do sistema
  • protege() - Protege a página
  • cadastrar() - Cadastra um usuário
  • VISIBILIDADE PROTEGIDA
  • lembrar() - Cria o cookie lembrar de mim
  • hash() - Gera o hash para a senha
  • retUsuario() - Retorna o usuário do banco de dados
  • salt() - Gera uma string aleatório

Note que estou usando dois tipos de visibilidade public e protected, a public permite que a função seja vista em qualquer lugar da aplicação, desde que a Classe esteja declarada (veremos isso mais a frente) e protected permite que a função seja usada apenas dentro da própria classe, ainda temos a private (que não estamos usando) que libera o uso dentro da própria classe e para as classes que estenderem a atual, mas isso é papo pra outro artigo.

Como conectar ao banco de dados com PHP PDO

Vamos aproveitar este tópico para criar dois atributos para nosso abjeto, atributos são as variáveis que declaramos logo no início, fora das funções. Vamos precisar de uma para armazenar o PDO e outra para as configurações do banco de dados, isso vai em baixo da chave de abertura da classe:

protected $mysql;
protected $db = array(
    'servidor'=>'localhost',
    'database'=>'blog_usuario',
    'usuario'=>'root',
    'senha'=>'',
);

Para chamar em qualquer lugar da classe basta prefixar com $this->, ou seja, para usar a variável $mysql escrevemos $this->mysql, e a $db, usamos $this->db, muito fácil.

Para fixar bem, a estrutura básica de um objeto no PHP Orientado a Objetos é:

  • Objeto (a classe)
  • Métodos (as funções)
  • Atributos (as variáveis)

Na função conectaBd() vamos chamar a classe PDO, ela é padrão do PHP então é só instanciar e usar, vou mostrar como instanciar a classe Usuarios mais pra frente então não vou entrar em detalhes aqui, para conectar no mysql usando PDO basta fazer assim:

$this->mysql = new PDO(
    'mysql:host=SERVIDOR;dbname=BANCODEDADOS', $USUARIO, $SENHA
);

Como estamos usando um array com os dados de configuração do $this->db, a nossa conexão vai ficar assim:

$this->mysql = new PDO(
    'mysql:host='.$this->db['servidor'].';dbname='.$this->db['database'], $this->db['usuario'], $this->db['senha']
);

Prontinho, usamos os atributos para setar a conexão ao banco de dados, o $this->db vai informar as configurações da conexão e o $this->mysql guardar nosso objeto PDO, então vou aproveitar para alterar o relatório de erros, por padrão ele vem com o ERRMODESILENT, acho o ERRMODEEXCEPTION mais produtivo, um dia desses falo disso aqui, por enquanto, insira isto logo abaixo da conexão que criamos acima (ainda dentro de conectaBd()):

$this->mysql->setAttribute(PDO::ATTRERRMODE, PDO::ERRMODEEXCEPTION);

Com isso nossa classe Usuarios já deve se conectar ao banco corretamente, mas isso de nada adianta se não tivermos consultas e uma tabela criada, vamos rodr esse sql no banco de dados:

CREATE TABLE IF NOT EXISTS `usuarios` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `nome` varchar(500) NOT NULL,
  `email` varchar(500) NOT NULL,
  `usuario` varchar(500) NOT NULL,
  `senha` varchar(500) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Fazendo consultas no Mysql utilizando o PDO

O PDO ficou famoso por seu nível de segurança, e agora você vai entender porque. A primeira coisa é criarmos nossa string de sql, mas em vez de passar os valores vamos usar parâmetros nomeados.

$sql='SELECT * FROM `usuarios` WHERE `usuarios`.`usuario` = :usuario ;';

Então nesse exemplo (que deve ficar dentro de retUsuario()) passamos um :usuario em vez de passar o valor do usuário em si, normalmente eu usaria SELECT * FROM usuarios WHERE usuarios.usuario = Erik ; para retornar os dados do usuário Erik, mas o PDO nos permite passar os valores em separado, ganhamos assim mais segurança e organização com o recurso bindValue que defini os valores para o parâmetro nomeado (no caso o :usuario), o bindValue var reforçar a segurança ainda mais permitindo que possamos definir o tipo do atributo, se é número (int) ou texto (string) além de evitar o famoso sql injection, veja como fazer:

Cole isso dentro do retUsuario()

$sql='SELECT * FROM `usuarios` WHERE `usuarios`.`usuario` = :usuario ;';

//prepara o sql para o bindValue
$mysql=$this->mysql->prepare($sql);

//Seta a variável $usuario de forma segura
$mysql->bindValue(':usuario', $usuario,PDO::PARAM_STR);

//Executa o sql
$mysql->execute();

//Retorna o resultado
return $mysql->fetch(PDO::FETCH_ASSOC);

Note que eu usei uma variável chamada $mysql, como trabalhamos com orientação a objeto esta variável não tem nada a ver com a $this->mysql, ou seja, não há herança (fiz isso de propósito, para você entender), viu como nosso login PHP é interessante e complexo. No retUsuario() acrescente a variável $usuario, assim: protected function retUsuario($usuario).

Use o mesmo processo para o método cadastrar().

public function cadastrar()
{
    if ($_SERVER['REQUEST_METHOD']=='POST') {
        $sql='INSERT INTO `usuarios` (`nome`,`email`,`usuario`,`senha`) VALUES (:nome,:email,:usuario,:senha);';
        $mysql=$this->mysql->prepare($sql);
        $mysql->bindValue(':nome', $_POST['nome'],PDO::PARAM_STR);
        $mysql->bindValue(':email', $_POST['email'],PDO::PARAM_STR);
        $mysql->bindValue(':usuario', $_POST['usuario'],PDO::PARAM_STR);
        $mysql->bindValue(':senha', $this->hash($_POST['senha']),PDO::PARAM_STR);
        $mysql->execute();
        header('Location: confirma-cadastro.html');
    }
}

E aproveito para redirecionar para a confirmação de cadastro.

Note o $SERVER['REQUESTMETHOD']=='POST' para saber o tipo de requisição, para o pessoal que sempre me pergunta isso.

Como trabalhar com Bcrypt no PHP

O bcrypt (ou Blowfish) é um método de encriptação de senhas de mão única, ou seja, uma vez aplicado, não tem volta, a vantagem dele sobre outros métodos é que nativamente pede um salt e um custo, na minha opinião, nada impede que você consiga o mesmo nível de segurança utilizando md5 ou sha1, sha512, Salsa20... a diferença é que o bcrypt já tem o custo e o salt para funcionar.

Para quem não sabe, o salt é uma string que complementa a senha passada de forma a gerar um hash diferente para senhas iguais, e o custo é o tempo que o servidor demora para processar a senha, que quanto mais demorar melhor para nós no caso de uma tentativa de invasão, o que convenhamos é uma premissa básica em um login PHP.

O salt do bcrypt precisa ter 22 caracteres formado por letras maiúsculas, minúsculas e números, já o custo precisa ser um número de dois caracteres entre 04 e 31.

Para satisfazer as necessidades do bcrypt eu vou usar dois métodos, o hash(), que vai retornar o bcrypt pronto, e o salt(), que vai gerar meu salt de 22 caracteres formados de letras maiúsculas, minúsculas e números.

protected function salt()
{
    $string = 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789';
    $retorno = '';
    for ($i = 1; $i <= 22; $i++) {
        $rand = mt_rand(1, strlen($string));
        $retorno .= $string[$rand-1];
    }
    return $retorno;
}
protected function hash($senha)
{
    return crypt($senha, '$2a$10$' . $this->salt() . '$');
}

Como você deve ter notado para gerar o bcryp basta usar a função crypt do PHP com a senha no primeiro parâmetro e a string $2a$CUSTO$SALTDE22CARACTERES$ no segundo, apenas isso, estou usando custo 10 aqui, fique a vontade para testar no seu servidor, mas pode exceder o processamento).

Para verificar a senha é bem simples também, basta você usar o crypt novamente:

if (crypt($senha_enviada, $senha_salva) === $senha_salva) {
    //logado com sucesso
} else {
    // não pode logar
}

Trabalhando com seções no PHP

Para setar uma seção no PHP é muito fácil, é só usar a variável $SESSION['nome da seção'], mas antes você precisa declarar sessionstart(), apenas isso.

Já para declarar cookies você precisa usar uma função chamada setcookie(), ele tem 4 parâmetros, o nome do cookie, o valor do cookie, o tempo de vida (opcional) e o endereço do domínio (opcional) ao qual o cookie pertence, depois de criado ele pode ser acessado como a seção, mas usando $_COOKIE['nome do cookie'], muito fácil.

Veja como ficaria nosso login php:

public function login()
{
//inicia a seção
    session_start();

    //checa o tipo da requisição
    if ($_SERVER['REQUEST_METHOD']=='POST') {

        //faz a requisição dos dados do usuário atual
        $usuario=$this->retUsuario($_POST['usuario']);

        //Se a senha bater
        if (crypt($_POST['senha'], $usuario['senha']) === $usuario['senha']) {
            //crio a seção de usuário
            $_SESSION["usuario"] = $usuario;

            //se eu enviei o lembrar
            if (!empty($_POST['lembrar'])) {
                //chamo a função lembrar
                $this->lembrar($usuario['senha']);
            }
        }

    //se a requisição não for post e existir os cookies de senha e usuario
    } elseif ((!empty($_COOKIE['usuario'])) and (!empty($_COOKIE['senha']))) {

        desencripto os dados
        $cookie['usuario'] = base64_decode(substr($_COOKIE['blog_ghj'],22,strlen($_COOKIE['usuario'])));
        $cookie['senha'] = base64_decode(substr($_COOKIE['blog_ghk'],22,strlen($_COOKIE['senha'])));

        //pego os dados do usuáriono banco
        $usuario=$this->retUsuario($cookie['usuario']);

        //verifico a senha
        if ($cookie['senha']==$usuario['senha']) {
            //crio a seção
            $_SESSION["usuario"] = $usuario;
        }

    }

    //aproveitei e criei uma função para redirecionamento
    if (!empty($_SESSION["usuario"])) {
        if (empty($_SESSION["url"])) {
            header('Location: index.html');
        } else {
            header('Location: '.$_SESSION["url"]);
        }
    }
}

E a função lembrar que cria os cookies:

protected function lembrar($senha)
{
    $cookie=array(
        'usuario'=>$this->salt().base64_encode($_POST['usuario']),
        'senha'=>$this->salt().base64_encode($senha)
    );
    setcookie('blog_ghj', $cookie['usuario'], (time() + (15 * 24 * 3600)),$_SERVER['SERVER_NAME']);
    setcookie('blog_ghk', $cookie['senha'], (time() + (15 * 24 * 3600)),$_SERVER['SERVER_NAME']);
}

Para o logout eu preciso destruir a seção e setar o cookie com valor vazio, para a seção é só usar sessionunset() e sessiondestroy() (sempre começando com session_start()), já o cookie é só criar o setcookie() novamente, mas passando apenas o primeiro parâmetro:

public function logout()
{
    session_start();
    session_unset();
    session_destroy();
    setcookie('blog_ghj');
    setcookie('blog_ghk');
    header('Location: index.html');
}

E por fim a função que vai proteger as páginas:

public function protege()
{
    session_start();
    if (empty($_SESSION["usuario"])) {
        $_SESSION["url"]=$_SERVER['REQUEST_URI'];
        header('Location: login.php');
    }
}

Nela eu só verifico se a seção "usuario" existe e se não existir eu apenas redireciono para a página de login armazenando a url atual em seção, assim eu posso redirecionar depois para a página pretendida.

Usando o script

Para começar a usar é bem simples, primeiro precisamos criar uma instancia do objeto Usuario, para tal vamos importar o arquivo nas páginas e em seguida armazenar em variável:

require 'php/Usuarios.php';
$usuarios=new Usuarios();

Bem simples, agora podemos chamar todos os métodos e atributos públicos da nossa classe bastando usar o traço + sinal de maior (formando uma seta "->"), seguido do que queremos, vamos fazer um teste, abra o arquivo cadastrar.php e insira no começo do arquivo entre tags do php () o seguinte:

require 'php/Usuarios.php';
$usuarios=new Usuarios();
$usuarios->cadastrar();

Veja, chamamos o método cadastrar, vamos fazer o mesmo com o resto dos arquivos:

login.php

require 'php/Usuarios.php';
$usuarios=new Usuarios();
$usuarios->login();

logout.php

require 'php/Usuarios.php';
$usuarios=new Usuarios();
$usuarios->logout();

pagina-protegida.php

require 'php/Usuarios.php';
$usuarios=new Usuarios();
$usuarios->protege();

E pronto, nosso login PHP está funcionando e instalado nas páginas, que tal testar?

Aqui os arquivos desta aula já finalizados

Se você quiser ir além e aprender mais em breve teremos um curso PHP MVC na prática, você vai descobrir como trabalhar de uma forma mais estruturada e prática.

Gostou do artigo? Compartilha.


Cursos relacionados


* Parcelamento apenas cartão de crédito! Pode haver uma pequena variação no parcelamento em relação a simulações apresentadas!