PHP é à quinta-feira – Sistema de Login com sessões
Por Pedro Peixoto para o PPLWARE
Em qualquer aplicação há necessidade de controlar o acesso dos utilizadores, de reservar algumas áreas apenas a administradores, de gerir quem pode ver o quê. Surge então a necessidade de implementar um sistema de autenticação e controlo de acessos. Vamos então aprender a fazer um simples sistema de logins.
Esta semana vamos continuar com os artigos simples, mas não tão curtos. Vamos alongar um pouco mais e aprender a fazer um simples sistema de autenticação e controlo de acessos. Obviamente, isto dava tema para uma tese, mas aqui vamos ser directos e objectivos.
Antes de começar a desenvolver o sistema propriamente dito vamos aqui cimentar alguns conhecimentos necessários:
Sessões
Seja mais simples, ou mais complexo um sistema de autenticação PHP recorre sempre às Sessões para o seu bom funcionamento.
As sessões permitem armazenar dados no servidor, não acessíveis ao utilizador, e que podem ser usados em diferentes páginas. Assim temos uma maneira simples e segura de guardar dados relativos a cada utilizador acessíveis em toda a aplicação.
Para usar as sessões do PHP temos que usar a seguinte função (em todas as páginas e antes de qualquer output):
<?php session_start(); ?> |
A partir deste momento podemos usar o array $_SESSION para guardar qualquer informação. Assim o seguinte código permite gravar o meu nome na minha sessão:
<?php session_start(); $_SESSION[‘NOME’]=’PEDRO’; ?> |
Agora, se fizermos o output desta variável em qualquer página o meu nome lá permanecerá:
<?php session_start(); echo $_SESSION[‘NOME’]; ?> |
Uma vez que vamos ter de chamar a função session_start em todas as páginas, é melhor guardar o código num ficheiro chamado “init.php”, depois basta inclui-lo em todas as páginas, assim, se no futuro for necessário alterar alguma coisa não precisamos de alterar em todas elas, mas apenas no “init.php”.
As sessões são temporárias, duram apenas um tempo predefinido nas configurações do PHP (pode ser alterado), daí as sessões expirarem. No entanto há também a necessidade de terminar uma sessão num determinado momento, principalmente quando elas guardam informação do utilizador e ele não quer que fique acessível a outro utilizador que possa usar o computador a seguir. Para terminar a sessão usamos a função “session_destroy()”. Claro que para fazer o “session_destroy” é necessário que previamente na página haja o “session_start”. Vamos então criar uma página “logout.php” que termine a sessão:
<?php include 'init.php'; session_destroy(); header("location: index.php"); ?> |
Usámos o ficheiro “init.php” que já tínhamos criado. De notar que na última linha redirecionamos o utilizador para a página “index.php” que é a página inicial de uma aplicação PHP.
POST:
O POST, de uma maneira fácil de entender, é um vector (tal como o $_SESSION) que nos permite aceder a dados enviados através de formulários HTML que usam este método. Para enviar dados de um formulário HTML para esta variável teremos que colocar o seguinte código HTML:
<html> <body> <form name="login" method="post" action="login.php"> </form> </body> </html> |
Desta maneira os dados dos campos do formulário (no exemplo não tem qualquer campo) serão enviados por POST para a página “login.php”.
Assim se pretendêssemos ler um username e uma password o código HTML seria:
<html> <head> <title>Login</title> </head> <body> <h2>Login</h2> <form name="login" method="post" action="login.php"> Email:<br/> <input name="email" type="text" maxlength="40"/></br> Password:</br> <input name="password" type="password" maxlength="20"/></br> <input type="submit" value="Login" /> </form> </body> </html> |
Vamos gravar esta página com o nome “index.php”.
Como já tínhamos visto os dados serão enviados por POST para a página “login.php”, então para testar temos de criar esta página:
<?php include 'init.php'; echo $_POST['email']; ?> |
Podemos agora testar:
E resultado depois de submeter:
Poderíamos fazer a validação com a password no próprio código mas como no último artigo já aprendemos a trabalhar com MySQL vamos então usar esses conhecimento para criar uma micro base de dados:
É recomendável que se usem passwords encriptadas, normalmente em MD5, mas vamos simplificar e criar algo simples. Um campo de Email e uma Password permitirão gerir os utilizadores do sistema. Vamos desde já adicionar um utilizador fictício:
Agora que já temos uma base de dados, vamos escrever o código que permita a ligação e consulta da mesma. Uma vez que já temos o ficheiro “init.php” poderemos colocar o código de conexão com o MySQL no mesmo, passando o ficheiro a ter a seguinte forma:
<?php session_start(); mysql_connect('localhost','root','root'); mysql_select_db('pplware'); ?> |
Agora vamos modificar o ficheiro “login.php” para poder consultar a base de dados e em caso de sucesso colocar o email em sessão:
<?php include 'init.php'; //CONSULTA DO UTILIZADOR $consulta="Select * from utilizadores where email='" . $_POST['email'] . "' and password='" . $_POST['password'] . "'"; $resultado=mysql_query($consulta); if (mysql_num_rows($resultado)>0) //SE O EMAIL E A PASSWORD COINCIDIREM { //COLOCA NA VARIAVEL LINHA OS DADOS DA CONSULTA $linha=mysql_fetch_array($resultado); //COLOCA O EMAIL EM SESSAO $_SESSION['EMAIL']=$linha['EMAIL']; //REDIRECCIONA A PAGINA PARA A PAGINA SECRETA header("location: admin.php"); } else //CASO NÃO COINCIDAM { //REDIRECCIONA PARA A PAGINA INICIAL REPORTANDO O ERRO header("location: index.php?erro=1"); } ?> |
Como podemos observar, primeiro fazemos uma consulta onde pedimos todos os registos que tenham o username e a password introduzidas, depois de contar o número de registos devolvidos, se existir algum registo significa que na base de dados existe a correspondência USERNAME<->PASSWORD introduzidas, guardamos o EMAIL em sessão e de seguida redireccionamos para a página de administração. Caso não existam registos que correspondam às credenciais introduzidas vamos direccionar para a página inicial com o parâmetro “erro” igual a 1. Vamos por isso configurar a página inicial e adaptá-la aos novos desenvolvimentos:
<?php include "init.php"; ?> <html> <head> <title>Login</title> </head> <body> <?php if(isset($_GET['erro'])) //SE EXISTIR ERRO echo 'Erro no login. Tente novamente.'; ?> <h2>Login</h2> <form name="login" method="post" action="login.php"> Email:<br/> <input name="email" type="text" maxlength="40"/></br> Password:</br> <input name="password" type="password" maxlength="20"/></br> <input type="submit" value="Login" /> </form> </body> </html> |
Assim, caso o parâmetro “erro” seja igual a 1 a página emite o erro 'Erro no login. Tente novamente.'.
Falta, agora controlar o acesso em páginas reservadas. Para exemplificar vamos criar uma página “admin.php” apenas acessível a utilizadores autenticados:
<?php include "init.php"; ?> <html> <head> <title>Área reservada</title> </head> <body> <?php if (isset($_SESSION['EMAIL'])) //SE EXISTIR AUTENTICAÇÃO { echo ' Olá ' . $_SESSION['EMAIL'] . '.<br/>'; //--------------------------// //TODO O CODIGO PRIVADO AQUI// //--------------------------// echo '<a href="logout.php"> Logout</a></td>'; //LINK PARA FAZER LOGOUT } else //CASO NÃO ESTEJA AUTENTICADO { echo 'Esta é uma área reservada, só utilizadores podem ter acesso.'; } ?> </body> </html> |
O código é simples, verificamos se a variável de sessão está a ser “usada” para decidir se é um utilizador autenticado ou não. Aproveitamos para deixar o link para a página de logout que já tínhamos criado num passo anterior.
No fundo este é o tipo de controlo que podemos usar em PHP, neste caso estamos a fazer um controlo simples onde apenas verificamos se é um utilizador autenticado ou não, mas poderíamos até ter colocado uma variável em sessão que dizia se o utilizador tinha permissões nível 1,2, ou 3 e nas páginas verificávamos se o nível era o exigido para a poder visualizar.
E está pronto o nosso sistema de login, resta experimentar e comprovar que tudo está a funcionar como deveria. Não é nada de muito avançado , mas tem as bases para desenvolverem o vosso próprio sistema.
Download: autenticação.zip
Este artigo tem mais de um ano
Excelente tutorial 🙂
já tive a experiência de usar estes codigos, mas quem precisar duma ajudinha, esta aqui um tutorial espectacular 🙂
Muito bem explicado, apesar de já conhecer esta forma de login acho que foi muito bem explicado.
Seria possível darem um exemplo recorrendo à encriptação MD5?
Muda esta linha:
$consulta=”Select * from utilizadores where email='” . $_POST[‘email’] . “‘ and password='” . $_POST[‘password’] . “‘”;
Para ficar assim:
$consulta=”Select * from utilizadores where email='” . $_POST[‘email’] . “‘ and password='” . md5(trim($_POST[‘password’])) . “‘”;
Obrigado. Já agora só mais uma pergunta, para poder usar a função md5() na verificação do login, a password tem de ser registada na BD já encriptada pela função md5 também?
Sim claro
Não deverias fazer o md5(trim($_POST[‘password’])), e sim o md5($_POST[‘password’]). A diferença? Se alguém tivesse um espaço no primeiro caracter ou no último, este iria desaparecer. Nunca se deveria fazer trim de uma password…
caso seja um projecto “a sério” recomendo a função sha1 (mais seguro que o md5) e adicionar uma string única à password (eg. username ou uma string aleatória guardada juntamente com o registo do utilizador na base de dados) para tornar as rainbow tables (tabelas de correspondências de hashes) inúteis caso o servidor seja comprometido.
Cumprimentos
Para um MD5, basta que graves a password na bd em MD5, e a quando o login convertes a password inserida em MD5 e comparas com a guardada na BD.
convertes assim:
md5(“$password”)
Cumps.
há algumas coisas no tutorial que não devem ser feitas, a password deve ficar codificada e a password comparada fora do query. permite injecção de sql
Para além de ser codificada, deve se-lo em forma de hash, de forma a nunca se conseguir obter a password original. E não usem md5 sff, isso já não é seguro faz tempo. Optem pelo sha ou sha2.
De qualquer das maneira, foi um bom post. Está bem explicado e é sem dúvida um tema interessante.
Boa noite,
Tal como eu disse inúmeras vezes no tutorial isto é um tutorial simples. Este tema dava para muita conversa. O objectivo aqui é dar as luzes de como funciona um sistema de login.
Como disse no início “Obviamente, isto dava tema para uma tese, mas aqui vamos ser directos e objectivos.”, este tutorial é destinado a quem não sabe fazer e por isso tem que ser simples, claro que para quem já entende alguma coisa, parece que não custa nada adicionar a encriptação da password, ou proteger contra sqlinjection mas quanto mais complicamos pior… Até assim este artigo já está mais extenso do que devia…
Cumprimentos
Tens uma grande falha de segurança, na parte de select à base de dados, não deves por o post directamente na query sem filtrares os dados.
Cheers
Deves usar sempre as funções sprintf() e mysql_real_escape_string() para ficares protegido contra injection:
// Query
$query = sprintf(“SELECT * FROM users WHERE user=’%s’ AND password=’%s'”, mysql_real_escape_string($user), mysql_real_escape_string($password));
// SQL
$sql = mysql_query($query) OR die(mysql_error());
http://php.net/manual/en/function.mysql-real-escape-string.php
Ou então alteras o php.ini logo de vez.
Certo, apenas não quis complicar. Estes comentários são bastante positivos, complementam o artigo, assim quem percebeu tudo e quer complementar pode ver o código melhorado. Faz-me lembrar as páginas de ajuda do PHP, tem o básico e por baixo podemos ver os comentários com melhorias, métodos de uso, etc… Obrigado, vocês acabam por serem co-autores dos artigos. 😀
Qual e’ a complexidade de codigo introduzida pelo uso de mysql_real_escape_string, que evita erros de sintaxe e SQL Injections, que um utilizador que pretende criar uma versao basica de login nao consegue compreender?
Versao 1:
$consulta=”Select * from utilizadores where email='” . $_POST[‘email’] . “‘ and password='” . $_POST[‘password’] . “‘”;
Versao 2:
$consulta=”Select * from utilizadores where email='” . mysql_real_escape_string($_POST[‘email’]) . “‘ and password='” . mysql_real_escape_string($_POST[‘password’]) . “‘”;
Fiquem Bem!
Não é nenhuma, eu até posso meter o código de um sistema de login todo XPTO, mas aqui o objectivo não é decorar código é aprender, compreender e perceber porque se faz assim. Estejam descansados que hade vir um artigo onde seja explicado o SQL injection e as maneiras de resolver e onde as funções sejam explicadas em pormenor. Quando aprendi, aprendi assim, começando por baixo e posso dizer que aprendi bem, por isso é assim que vou explicar porque na minha óptica é a melhor maneira de aprender. Obrigado pelo contributo.
Lê este artigo:
http://ilia.ws/archives/103-mysql_real_escape_string-versus-Prepared-Statements.html
Cumprimentos
Meu caro, a questão é que você já sabe. Para quem está aprendendo, muitas informações podem sim confundir, afinal quem procura esses assuntos geralmente quer aprender a fazer e não só copiar e colar.
Se for só para copiar e colar, você pode colocar 300 páginas de código que um garoto de 10 anos vai conseguir implantar o sistema de login. Mas se for para aprender e quem sabe um dia conseguir fazer sozinho tem que ser aos poucos mesmo.
A pessoa aprende o básico e depois vai incrementando.
Eu acho assim, muitas pessoas não querem dedicar seu tempo a criar tutoriais, mas sempre encontram uma forma de criticar o trabalho alheio. Acha que não está bom? Faça melhor. E não venha com essa de que está só querendo ajudar, ou acrescentar algo.
Muito obrigado,
Andei a semana passado a recolher algum conhecimento àcerca de sessões, e com este tutorial, fiquei mais esclarecido.
Por falar em passwords, parece que houve problemas no lastpass.com, já alterei todas as que são mais importantes e recomendo, por uma questão de segurança, que também o façam.
Já agora NUNCA passem a variável $_POST[‘variable’] directamente para a query, se implementarem um sistema destes recomendo uma vista de olhos no PDO
http://php.net/manual/en/book.pdo.php
Os prepared statements permitem evitar a possibilidade de sql injection ( e o PDO permite uma grande portabilidade de código entre sistemas de base de dados)
Cumprimentos
Já que se tocou neste assunto alguém pode-me dizer como faço para limitar o número de tentativas de login?
Ex: Falhou a pass 3x só pode fazer login daqui a 15min…
Eu tenho uma ideia, mas não sei se é a mais correcta… criar uma variável de sessão com o numero de segundos pretendidos. E depois criar um ciclo até que os segundos cheguem a zero.
Obrigado pelas rubricas.. estamos sempre a apreender xD
Boa noite, se queres que as tentativas sejam permanentes, podes adicionar um campo na base de dados com o número de tentativas falhadas, e quando falha incrementas o seu valor. Quando faz login verificas se o numero de tentativas já passou as 3 e não deixas fazer login.
Não quero permanente.. basta uns 15min.
Mas deste-me uma ideia melhor… gravo um número de tentativas e a hora da ultima tentativa de login e depois compara a hora a que está a tentar fazer o novo login com a hora que está na base dados (a da última tentativa). fiz-me entender?
(Viperz0r.. não existe nenhum link…)
Dá um vista de olhos, pode ser que seja isto que queiras.
Eu posso ajudar nisso! Já fiz isso em vários sistemas de login. Inclusive, verificar quantas vezes errou no mesmo IP, a que horas, etc…
Cumpts,
Mr. PI
Faço igual vcs ensinaram a algum tempo.
Simples e funcional.
Show de bola.
Bem que podia rolar umas aulas de CakePHP. =D
Parabéns 🙂
Boas pessoal!
Por acaso já alguém usou o SHA512 num sistema de login em PHP? Eu usei num sistema que criei e não tive qualquer tipo de problema.
Qual a vossa opinião do SHA512 para o SHA1?
Sinceramente acho que nos dias de hoje o uso da encriptação MD5 num sistema de login fica muito fraco.
Penso que o melhor a fazer para melhorar a segurança, é:
– Criar um SALT;
– SHA512;
– Encriptar a mesma password 10 ou mesmo 15 vezes (se for um sistema com poucos users, caso contrário pode puxar muito pela base de dados);
– Guardar o IP do cliente;
– Criar um sistema de logs;
– Enviar um email ao admin cada vez que houver uma tentativa errada de login…
Existem muitas coisas para melhorar a segurança.
Pessoal do PPLWARE, se quiserem posso exemplificar com código tudo o que disse. 🙂
Cumpts,
Mr. PI
Sha1 já é suficientemente seguro visto possuir possibilidades extremamente baixa de colisões, mas não se perde nada em recorrer ao sha-512 visto este recorrer a “palavras” maiores tornando ataques de dicionário ainda menos eficientes. Obviamente que o sha-512 é computacionalmente mais “pesado” que o sha-1, mas não é um grande problema porque as passwords não são suposto serem verificadas em cada carregamento de página (sessões de autenticação / tokens).
O salt pode-se recorrer ao nome do utilizador como prefixo da password antes de recorrer à síntese segura, mas para ser mais seguro recomendo uma cadeia de caracteres aleatória guardada juntamente com o registo do utilizador (tem que ser única para cada utilizador). O salt faz as tabelas de sínteses (rainbow tables) inúteis caso o servidor seja comprometido, porque o atacante terá que criar tabelas para cada utilizador, o que não é viável de realizar em tempo útil.
Cifrar varias vezes acaba por ser puro desperdício de recursos computacionais, porque na prática não garante nenhuma segurança.
Sistema de logs é altamente recomendado para detectar intrusões e modificações não autorizadas.
É preciso ter cuidado com o facto de cifrar várias vezes uma chave, fazer uma comparação com a BD, e de seguida ainda enviar um e-mail ao admin, ser computacionalmente muito mais pesado do que apenas efectuar o login. Ou seja o servidor vai ter muito mais trabalho do que o cliente. É meio caminho andado para entupir o servidor.
Esta opção que referi foi apenas a pensar na segurança. 🙂
Estas normas que referi, apenas são para sites que tenham poucos users, mas que a informação seja vital, por exemplo um backoffice de uma empresa, em que possam consultar dados criticos da mesma.
Ainda existem muitas mais normas que poderão ser incluidas no script de login, nomeadamente:
– Bloquear o user após X tentativas erradas e enviar email ao administrador com o IP, horas dos logins e passwords utilizadas (para vermos se foi utilizado tentativa por dicionário);
– Verificar se o login vem pelo método $_SERVER[‘REQUEST_METHOD’] == ‘POST’), caso contrário bloquear;
– Colocarem um sprintf() na query de à base de dados;
– Na query nunca esquecer de incluir “LIMIT 1”;
Como disse, existem N coisas que se podem fazer para melhorar bastante a segurança.
Nunca esquecer de efectuar o log de todos os passos que são efectuados pelo user!
Cumpts,
Mr. PI
Sim, mas dessa forma estás a facilitar um DoS. Isto não convém ser ignorado.
Como assim? Podes dar um exemplo pff?
Cumpts,
Mr. PI
Muito Obrigado pelo tutorial, veio mesmo a calhar.
Gostaria de perguntar se não é possível colocar a mensagem de erro do login através de uma box ao invés de aparecer na mesma pagina?
Cumprimentos,
Milhafre
Bom dia, sim é facil. Tens que colocar as tags script e depois chamar a função alert com a mensagem. Vou escrever aqui o código mas não sei se vai ser permitido:
Alteras o código no index.php:
echo ‘Erro no login. Tente novamente.’;
para:
echo ‘alert(“Erro no login. Tente novamente.”)’;
cumprimentos
Bom dia, sim é facil. Tens que colocar as tags script e depois chamar a função alert com a mensagem. Vou escrever aqui o código mas não sei se vai ser permitido:
Alteras o código no index.php:
echo ‘Erro no login. Tente novamente.’;
para:
echo ‘alert(“Erro no login. Tente novamente.”)’;
cumprimentos
Boa tarde, tenho criada uma BD com serca de 400 users, quando meto um utilizador errado ele dá erro (que está certo)…mas quando meto um user e senha correcta manda-me para a pagina admin.php, mas dá-me a mensagem “Esta é uma àrea reservada, só utilizadores podem ter acesso”, dá a impresão que perde a conexão…será que me pode ajudar?
Tambem tenho essa duvida ! alguem pode ajudar ?
Boa noite, vocês estão a iniciar a sessão correctamente? Incluiram o init.php?
Não consegui criar a BD, será que pode divulgar o código SQL ?
Abraços
Já a consegui criar 😀
Agora quando faço login não me redirecciona para a página de admin…
Abraços
O Português só se contenta a criticar e sentir-se o maior… É por isso que em Portugal(Bom país para desenvolvimento de tecnologias) se comem uns aos outros em vez de se ajudarem uns aos outros.
Pedro, parabéns. Lembro-me que na minha altura os “form’s” eram simplesmente
$user_bd=mysql_query(….);
if ($_POST[‘user’]!=$user_db){print ‘Wrong Username!’;
}else{
if (…..
Cumps
Muito bom ! Coloquem tudo em ficheiros e partilhem em http://www.mais-codigo.com ! 🙂
Alguém poderia dar um exemplo com níveis??!!
Viva Pedro!
Pretendes ajuda com níveis em que aspecto?
Deves querer algo do género:
If user XPTO == Admin -> Tem acesso à página 1, 2, 3
If user XPTO == Power Member -> Tem acesso à página 1, 2
If user XPTO == User -> Tem acesso à página 1
Certo?
é isso mesmo…
Quero criar o esquema de login e senha, mas fiz o passo-a-passo do seu vídeo e não consegui, tem como vc me passar seu e-mail ou msn para tentar tirar algumas dúvidas?
Basta baixar os arquivos no meu ftp?
Bom e muito esclarecedor, parabens pelo tópico,
e isso acabou dando pano pra manga e abrir um novo tópico a respeito de segurança em logins imputs de entrada, text area.
Boas,
Alguem pode dar o ficheiro SQl?
Boa tarde a todos! Peço desculpa pela minha ignorância no assunto mas efectivamente não sei muito sobre PHP ainda (infelizmente!). Estou a fazer um site em WordPress e, em algumas páginas existe conteúdo que pode ser visto por diferentes utilizadores. Por exemplo: numa página com uma introdução + lista de links…a introdução pode ser vista por todos os utilizadores mas a lista de links apenas por 2 dos 3 tipos de utilizadores. Não sei como fazer isto! Alguém pode dar uma ajuda pf? Obrigada!
Tenho uma dúvida: como faço para criar um sistema de login que quando o usuário registrar (criar um login de acesso), cadastrar como TRIAL com 15 dias?
O usuário terá 15 dias para ter acesso aos conteúdos restritos e, quando passar esse tempo, o acesso bloqueia e aparece uma mensagem de “Compre para continuar acessando”
Pedro, em qual email posso te enviar um código de login e senha para que vc possa estar me ajudando?
Boas
Tenho uma duvida como e que eu autentico o utilizador???
De resto esta tudo óptimo.
Obrigado.
Olá, eu to montando um sistema de login , quando inicio a sessão ele exibir a msg de bem vindo ao usuario gostaria de saber como faço para a pagina php buscar dados de outras tabelas pele id que foi atribuída ao usuarios, exemplo, tenho uma tabela de endereço e de usuario na tabela, quando ele se registrar no site o user vai para uma tablela junto com a senha e o endeço cpf reg para outra. gostaria de buscar essas informações e exibir na tela !
Alguém me sabe dizer porque o Xampp dá este erro, ” Call to undefined function mysql_connect()”?
Obrigado
mysqli_select_db() expects parameter 1 to be mysqli, string
o meu codigo é este:
Consegui descobrir a solução. Basicamente a ordem dos parâmetros estava errada.
No php.ini vê se estas a fazer o load da dll
extension=php_mysql.dll
MD5 e SHAx são algoritmos de hashing e não de cifra!
Boa noite,
Alguem me pode ajudar de como posso fazer para que alguns menus estejam visiveis para o utilizador e todos estejam visiveis para o administrador?