Diferencia entre revisiones de «PHP Validando usuarios»

De MediaWiki
Ir a la navegación Ir a la búsqueda
 
(No se muestran 23 ediciones intermedias de 2 usuarios)
Línea 8: Línea 8:
  
 
* Neste punto imos ver como podemos facer para que o usuario non poida entrar nas páxinas se non está validado.
 
* Neste punto imos ver como podemos facer para que o usuario non poida entrar nas páxinas se non está validado.
−
: Como aínda non sabemos 'conectar' con bases de datos imos supoñer que o usuario ten que ter de nome 'pepe' e de password '12345'. En caso doutra combinación supoñemos que o usuario non está autorizado para acceder ao sitio web.
 
−
 
 
: Fixarse que estamos implementando un mecanismo de autenticación de usuarios.  
 
: Fixarse que estamos implementando un mecanismo de autenticación de usuarios.  
 
: Non indicamos nada acerca dos permisos que ten dito usuario. Por exemplo, un administrador e un usuario son usuarios que teñen dereito de conexión ao sitio web, pero cada un deles terá permisos diferentes para poder realizar accións distintas...
 
: Non indicamos nada acerca dos permisos que ten dito usuario. Por exemplo, un administrador e un usuario son usuarios que teñen dereito de conexión ao sitio web, pero cada un deles terá permisos diferentes para poder realizar accións distintas...
  
−
== Exemplo práctico ==
 
  
 
<br />
 
<br />
−
* Partimos por tanto dunha páxina de validación cun formulario.
+
* '''<u>IMPORTANTE:</u>''' Durante o proceso de autenticación, o navegador envía ao servidor o usuario/password introducido polo usuario. <u>Eses datos deben ir cifrados</u> para que non poidan ser obtidos de forma fraudulenta pola rede. Para iso é necesario empregar '''certificados dixitais''' que no caso das páxinas web, tradúcese en empregar unha conexión '''https''' no que o servidor web emite un certificado dixital váliado que o indentifica.
 +
: Nunca se debe desenvolver unha aplicación web no que non se empregue este tipo de certificado.
 +
: Como facelo queda fora dos contidos deste curso, polo que as probas que estades a desenvolver empregan conexións non seguras de tipo http.
 +
 
 +
==Exemplo rexistro/validación de usuario e carrito da compra==
 +
 
 +
* Imos facer un pequeno sitio web no que imos poder rexistar novos usuarios e despois mediante unha validación imos poder ter acceso as páxinas para mercar produtos e xestionar un carrito da compra
 +
 
 +
 
 +
 
 +
 
 +
<br />
 +
===Rexistro de usuarios===
  
−
: Nome da páxina: '''UD4_Exemplo1_Validacion_Formulario.php'''
+
 
−
::<syntaxhighlight lang="java" line enclose="div" highlight="2,3" >
+
 
 +
* Nesta páxina vaise amosar un login/password e vaise rexistrar un usuario/password.
 +
: Primeiro temos que crear a táboa usuarios no Mysql:
 +
:::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
CREATE TABLE `php`.`usuarios` (
 +
  `id` INT NOT NULL AUTO_INCREMENT,
 +
  `login` VARCHAR(50) NOT NULL,
 +
  `password` VARCHAR(255) NOT NULL,
 +
  PRIMARY KEY (`id`),
 +
  UNIQUE INDEX `login_UNIQUE` (`login` ASC) VISIBLE);
 +
</syntaxhighlight>
 +
 
 +
: Nesta táboa o password vai estar encriptado cunha función de hash que en principio devolve como mínimo 60 caracteres, pero que pode chegar a devolver ata 255 caracteres, por se nun futuro cambia a implementación.
 +
 
 +
 
 +
<br />
 +
* Agora necesitamos gardar usuarios/password nesa táboa. Para iso imos facer uso das funcións:
 +
:* [https://www.php.net/manual/en/function.password-hash password_hash()] => Para gardar a contrasinal
 +
:* [https://www.php.net/manual/en/function.password-verify.php password_veriry()] => Para comprobar a contrasinal
 +
 
 +
* Engadimos ao arquivo bd.inc.php que queda fora da ruta onde apunta o sitio web, a orde para engadir un usuario. Lembrar que isto [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Gardando_os_datos_de_conexi.C3.B3n_nun_lugar_seguro xa o vimos na UD3].
 +
: Podemos empregar un procedemento almacenado de Mysql ou a orde SQL Insert. Lembrar que xa expliquei que é moito máis seguro empregar o procedemento ou ordes prepare.
 +
'''Arquivo /var/www/confBD/bd.inc.php'''
 +
:::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
define('INSERIR_USUARIOS',"INSERT INTO usuarios (login,password) VALUES ('%s','%s')");
 +
</syntaxhighlight>
 +
 
 +
 
 +
* Creamos unha páxina php para dar de alta usuarios.
 +
: Como queremos enviar a esta páxina información se o rexistro foi correcto ou se houbo algún tipo de erro, imos crear un include no que se amosarán o contido de dúas variables de sesión que imos empregar para isto:
 +
 
 +
'''Arquivo UD4_Exemplo1_Mensaxes.inc.php'''
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
        <?php
 +
            if (!empty($_SESSION['msg'])){
 +
                printf("<div>INFORMACIÓN: <b>%s</b>",$_SESSION['msg']);
 +
            }
 +
            if (!empty($_SESSION['error'])){
 +
                printf("<div>ERROR: <b>%s</b>",$_SESSION['error']);
 +
            }
 +
            unset($_SESSION['msg']);
 +
            unset($_SESSION['error']);
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
 
 +
<br />
 +
: '''Arquivo UD4_Exemplo1_Rexistro_Formulario.php'''
 +
::<syntaxhighlight lang="java" enclose="div" highlight="2,3,47" >
 
<?php
 
<?php
 
     session_start();
 
     session_start();
 
     unset($_SESSION['autenticado']);
 
     unset($_SESSION['autenticado']);
−
      
+
     $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // Para evitar ataques csrf
 
?>
 
?>
 +
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
Línea 48: Línea 107:
 
     </head>
 
     </head>
 
     <body>
 
     <body>
−
         <form id="frmValidacion" class="formularios" method="post" action="UD4_Exemplo1_Validacion_Usuarios.php">
+
         <form id="frmValidacion" class="formularios" method="post" action="procesar_UD4_Exemplo1_Rexistro_Usuarios.php">
 +
            <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
 
             <div>     
 
             <div>     
 
                 <div class='etiqueta'>Login:</div>
 
                 <div class='etiqueta'>Login:</div>
−
                 <input type="text" size="15" maxlength="15" name="txtLogin" />
+
                 <input type="text" required size="15" maxlength="15" name="txtLogin" />
 
             </div>                 
 
             </div>                 
 
             <div>
 
             <div>
 
                 <div class='etiqueta'>Password:</div>
 
                 <div class='etiqueta'>Password:</div>
−
                 <input type="password" size="15" maxlength="15" name="txtPassword" />
+
                 <input type="password" required size="15" maxlength="15" name="txtPassword" />
 
             </div>                 
 
             </div>                 
 
             <div class="botonesformularios">
 
             <div class="botonesformularios">
−
                 <input type='submit' value="ENVIAR" />
+
                 <input type='submit' value="ENVIAR"  name="btnRexistrar" />
 +
                <input type='reset' value="LIMPAR" name="btnLimpar" />
 
             </div>
 
             </div>
 
         </form>
 
         </form>
  
 +
        <?php
 +
        include('UD4_Exemplo1_Mensaxes.inc.php')
 +
        ?>
 
     </body>     
 
     </body>     
 
</html>
 
</html>
 +
 +
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
: Liña 2: '''Sempre debemos ter o session_start() ao comezo da páxina''' se imos traballar con variables de sesión.
 
: Liña 2: '''Sempre debemos ter o session_start() ao comezo da páxina''' se imos traballar con variables de sesión.
 
: Liña 3: Dita variable de sesión estará explicada posteriormente. Adiantar que é a variable que imos utilizar para deixar visualizar ao usuario as páxinas protexidas (que requiran unha autenticación previa).
 
: Liña 3: Dita variable de sesión estará explicada posteriormente. Adiantar que é a variable que imos utilizar para deixar visualizar ao usuario as páxinas protexidas (que requiran unha autenticación previa).
−
 
+
: Liña 47: Facemos o include das mensaxes de erro e aviso se se producen na páxina de procesar.
 
<br />
 
<br />
 
: Visualizamos isto:
 
: Visualizamos isto:
Línea 76: Línea 142:
  
  
−
* O seguinte será validar o usuario e comprobar que coincide có usuario/password indicado.
+
<br />
 +
* O seguinte será rexistrar o usuario/password na táboa usuarios.
 +
: Para iso imos facer uso da [https://www.php.net/manual/es/function.password-hash.php función password_hash()] que permite encriptar un texto cun cifrado e vai ser a password a gardar na táboa de usuarios.
 +
: Para comprobar o usuario cando intente entrar empregaremos [https://www.php.net/manual/es/function.password-verify.php password_verify()].
 +
 
  
−
: Nome da páxina: '''UD4_Exemplo1_Validacion_Usuarios.php'''
+
<br />
−
::<syntaxhighlight lang="java" line enclose="div" highlight="2,14-17" >
+
: ''' Arquivo procesar_UD4_Exemplo1_Rexistro_Usuarios.php'''
 +
::<syntaxhighlight lang="java" enclose="div" highlight="8,13,28" >
 +
<?php
 +
/* Lembrar quitar isto ao probar o scripts sen erros */
 +
ini_set('display_errors', 1);
 +
ini_set('display_startup_errors', 1);
 +
error_reporting(E_ALL);
 +
 
 +
 
 +
session_start();
 +
 
 +
if (!isset($_POST['btnRexistrar']) || ($_POST['csrf_token'] !== $_SESSION['csrf_token'])){    // Se non vimos de premer o botón de rexistro volvemos a login. O csrf_token para evitar ataques
 +
    unset($_SESSION['csrf_token']);
 +
    header("location:UD4_Exemplo1_Rexistro_Formulario.php");
 +
    exit();
 +
}
 +
 
 +
require ('/var/www/confBD/bd.inc.php');
 +
 
 +
try {
 +
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); 
 +
    $conex->set_charset('utf8mb4');
 +
    // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion
 +
    if ((strlen($_POST['txtLogin'])<4) || (strlen($_POST['txtPassword'])<4)){
 +
        $_SESSION['error'] = sprintf("A lonxitude do login e password non poden ter menos de 4 caracteres...");
 +
    }
 +
    else {
 +
        // Eliminamos caracteres non permitidos
 +
        $login = filter_var($_POST['txtLogin'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
 +
        $password = password_hash($_POST['txtPassword'],PASSWORD_DEFAULT);
 +
 
 +
        // Escapamos as comillas simples e dobres
 +
        $login = $conex->real_escape_string(strtoupper($login));
 +
        $ordeSQL=sprintf(INSERIR_USUARIOS,$login,$password);
 +
        $conex->query($ordeSQL);
 +
 
 +
        $_SESSION['msg'] = sprintf("Usuario rexistrado correctamente.");       
 +
    } 
 +
       
 +
}  // Pecha o try da parte superior da páxina. Lembrar que aos usuarios non se lle deben amosar os erros. Isto está posto a nivel informativo para os programadores
 +
catch (mysqli_sql_exception $e) {
 +
    $_SESSION['error']=sprintf("Erro mysql: %s",htmlentities($e->getMessage()));    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
 +
} 
 +
catch (Expcetion $e) {
 +
    $_SESSION['error']=sprintf("Erro xeral: %s",htmlentities($e->getMessage()));    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
 +
}
 +
finally{
 +
    if(!empty($conex)){
 +
        $conex->close();
 +
    }
 +
}
 +
 
 +
unset($_SESSION['csrf_token']); // Para evitar ataques csrf
 +
 
 +
$url = sprintf("UD4_Exemplo1_Rexistro_Formulario.php");
 +
header("Location:" . $url);
 +
 
 +
?>     
 +
</syntaxhighlight>
 +
 
 +
 
 +
: Liña 8: Fixarse que despois da orde 'header' hai un exit para que o script non continúe executándose. Isto é así xa que o header non se enviará ao cliente ata que non remate o script. Dependerá de nos como programadores se queremos 'facer' algo máis antes de redirixir a páxina do navegador do cliente.
 +
: Liña 13: '''Sempre debemos ter o session_start() ao comezo da páxina''' se imos traballar con variables de sesión.
 +
: Liña 28: Encriptamos o password.
 +
 
 +
 
 +
 
 +
 
 +
 
 +
 
 +
<br />
 +
 
 +
===Validando usuarios===
 +
 
 +
* Agora que xa podemos rexistrar usuarios imos crear un formulario de login.
 +
: O código será o mesmo que o de rexistro, cambiando o action do formulario (poderiamos poñer a mesma páxina cunha variable cambiando o action en función do que páxina se cargue).
 +
 
 +
 
 +
 
 +
'''Arquivo UD4_Exemplo1_Login_Formulario.php'''
 +
::<syntaxhighlight lang="java" line enclose="div" highlight="30" >
 
<?php
 
<?php
 
     session_start();
 
     session_start();
−
      
+
     unset($_SESSION['autenticado']);
−
     // Con isto impedimos que poidan cargar directamente a páxina ou
+
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // Para evitar ataques csrf
−
    // que non se envíen datos
+
?>
−
    if (!isset($_POST['txtLogin']) || !isset($_POST['txtPassword']) ||
+
 
−
        empty($_POST['txtLogin']) || empty($_POST['txtPassword'])){
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
−
        header("location:UD4_Exemplo1_Validacion_Formulario.php");
+
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
−
        exit();
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
−
    }
+
    <head>
−
    // Eliminamos caracteres
+
        <meta charset="utf-8" />
−
    $usuario = filter_var($_POST['txtLogin'],FILTER_SANITIZE_STRING);
+
        <title>Formulario</title>
−
    $password = filter_var($_POST['txtPassword'],FILTER_SANITIZE_STRING);
+
        <style>
 +
            .formularios{
 +
                width: 30%;
 +
                margin: 0px auto;
 +
            }
 +
            .etiqueta{
 +
                float: left;
 +
                width: 190px;
 +
            }
 +
            .botonesformularios{
 +
                width: 50%;
 +
                margin: 30px auto;
 +
                text-align: center;
 +
            }
 +
        </style>
 +
    </head>
 +
    <body>
 +
        <form id="frmValidacion" class="formularios" method="post" action="procesar_UD4_Exemplo1_Login_Usuarios.php">
 +
            <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
 +
            <div>   
 +
                <div class='etiqueta'>Login:</div>
 +
                <input type="text" required size="15" maxlength="15" name="txtLogin" />
 +
            </div>               
 +
            <div>
 +
                <div class='etiqueta'>Password:</div>
 +
                <input type="password" required size="15" maxlength="15" name="txtPassword" />
 +
            </div>               
 +
            <div class="botonesformularios">
 +
                <input type='submit' value="ENVIAR"  name="btnRexistrar" />
 +
                <input type='reset' value="LIMPAR" name="btnLimpar" />
 +
            </div>
 +
        </form>
 +
 
 +
        <?php
 +
        include('UD4_Exemplo1_Mensaxes.inc.php')
 +
        ?>
 +
     </body>   
 +
</html>
 +
 
 +
 
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
* Agora necesitamos comprobar se o login-password está rexistrado.
 +
: Para iso creamos no arquivo bd.inc.php a orde SQL que vai comprobar isto.
 +
'''Arquivo bd.inc.php'''
 +
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
define('VALIDAR_USUARIOS', "SELECT password FROM usuarios WHERE login='%s'");  // O login non debería poder repetirse ao rexistrar un usuario
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
* E por último, creamos a páxina que vai validar dito usuario. En caso de que exista e sexa correcto o login/password, empregaremos a variable de sessión $_SESSION['autenticado'] para darlle o valor do login e cargaremos unha páxina de nome '''consultar_exemplares_por_libro_con_validacion.php''' baseada [https://wiki.cifprodolfoucha.es/index.php?title=PHP_UD3_OP_CONSULTA#Caso_pr.C3.A1ctico:_Enviando_par.C3.A1metros_de_tipo_num.C3.A9rico nunha páxina feita na UD3].
 +
: A variable a empregaremos para chequear todas as páxinas nas que se necesite que o usuario estea autentificado.
 +
 
 +
 
 +
'''Arquivo procesar_UD4_Exemplo1_Login_Usuarios.php'''
 +
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
<?php
 +
/* Lembrar quitar isto ao probar o scripts sen erros */
 +
ini_set('display_errors', 1);
 +
ini_set('display_startup_errors', 1);
 +
error_reporting(E_ALL);
 +
 
 +
session_start();
 +
 
 +
if (!isset($_POST['btnRexistrar']) || ($_POST['csrf_token'] !== $_SESSION['csrf_token'])){    // Se non vimos de premer o botón de rexistro volvemos a login. O csrf_token para evitar ataques
 +
    unset($_SESSION['csrf_token']);
 +
    header("location:UD4_Exemplo1_Login_Formulario.php");
 +
    exit();
 +
}
 +
unset($_SESSION['csrf_token']); // Para evitar ataques csrf
 +
 
 +
require ('/var/www/confBD/bd.inc.php');
  
−
     if (($usuario=='pepe') && ($password=='12345')){
+
try {
−
         $_SESSION['autenticado']=1;
+
     $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); 
−
        header('location:UD4_Exemplo1_Validado.php');
+
    $conex->set_charset('utf8mb4');
−
        exit();
+
    // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion
 +
    if ((strlen($_POST['txtLogin'])>50)){
 +
         $_SESSION['error'] = sprintf("A lonxitude do login non pode ser maior a 50 caracteres...");
 
     }
 
     }
 
     else {
 
     else {
−
         $_SESSION['autenticado']=0;
+
        // Eliminamos caracteres non permitidos
−
         header('location:UD4_Exemplo1_Validacion_Formulario.php');
+
        $login = filter_var($_POST['txtLogin'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
−
         exit();
+
        $password = $_POST['txtPassword'];
 +
 
 +
        // Escapamos as comillas simples e dobres
 +
        $login = $conex->real_escape_string(strtoupper($login));
 +
         $ordeSQL=sprintf(VALIDAR_USUARIOS,$login,$password);
 +
        $result = $conex->query($ordeSQL);
 +
        if ($result && $result->num_rows>0){
 +
            $row = mysqli_fetch_assoc($result);
 +
            $result->free();  // Liberamos da memoria os recursos
 +
            if(password_verify($password, $row['password'])){    // Usuario/Password correcto
 +
                $_SESSION['autenticado']=$login;    // Permite saber se está autenticado no resto de páxinas
 +
                header("Location:UD4_consultar_exemplares_por_libro_con_validacion.php");
 +
                exit();
 +
            }
 +
            else{
 +
                $_SESSION['error']=sprintf("Usuario ou password non correctos");    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
 +
                header("Location:UD4_Exemplo1_Login_Formulario.php");
 +
                exit();
 +
            }
 +
        }
 +
         else{
 +
            $_SESSION['error']=sprintf("Usuario ou password non correctos");    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
 +
            header("Location:UD4_Exemplo1_Login_Formulario.php");
 +
            exit();
 +
        }
 +
 
 +
    } 
 +
       
 +
}  // Pecha o try da parte superior da páxina. Lembrar que aos usuarios non se lle deben amosar os erros. Isto está posto a nivel informativo para os programadores
 +
catch (mysqli_sql_exception $e) {
 +
    $_SESSION['error']=sprintf("Erro mysql: %s",htmlentities($e->getMessage()));    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
 +
} 
 +
catch (Expcetion $e) {
 +
    $_SESSION['error']=sprintf("Erro xeral: %s",htmlentities($e->getMessage()));   // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
 +
}
 +
finally{
 +
    if(!empty($conex)){
 +
         $conex->close();
 
     }
 
     }
 +
}
 +
 +
unset($_SESSION['csrf_token']); // Para evitar ataques csrf
 +
$url = sprintf("UD4_Exemplo1_Login_Formulario.php");
 +
header("Location:" . $url);
 +
 +
?>     
 +
 +
</syntaxhighlight>
 +
 +
 +
 +
 +
* Agora soamente temos que verificar en todas as páxinas que necesiten autentificación a variable de sesión
 +
'''Arquivo UD4_consultar_exemplares_por_libro_con_validacion.php'''
 +
::<syntaxhighlight lang="java" line enclose="div" highlight="9-11,29-31" >
 +
<?php
 +
/* Lembrar quitar isto ao probar o scripts sen erros */
 +
ini_set('display_errors', 1);
 +
ini_set('display_startup_errors', 1);
 +
error_reporting(E_ALL);
 +
 +
session_start();
 +
 +
if(!isset($_SESSION['autenticado'])){
 +
    header("Location:UD4_Exemplo1_Login_Formulario.php");
 +
}
 +
 +
require ('/var/www/confBD/bd.inc.php');
 +
 +
try {
 +
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);  // Indico o nome da bd
 +
    $conex->set_charset('utf8mb4');
 +
 
?>
 
?>
 +
 +
<!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" lang="es" xml:lang="es">
 +
    <head>
 +
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 +
        <title>Operacións BD</title>
 +
    </head>
 +
 +
    <body>
 +
        <div style='float:right; margin:20px'>
 +
            <a href='UD4_Exemplo1_validacion_sair.php'>Saír da aplicación</a>
 +
        </div>
 +
    <?php       
 +
       
 +
        $result=$conex->query(CONSULTAR_LIBROS);
 +
        if ($result && $result->num_rows>0){
 +
            printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
 +
            echo "<select name='lstLibros' onChange='this.form.submit();'>";
 +
            echo "<option value='-1'></option>";
 +
            while($row = $result->fetch_assoc()) {
 +
                $cadea = '';
 +
                if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){  // Se o libro seleccionado é igual ao id da fila, o seleccionamos en html
 +
                    $cadea="selected='selected'";
 +
                }
 +
 +
                printf("<option value='%d' %s>%s</option>",htmlentities($row["id"]),$cadea,htmlentities($row["titulo"]));
 +
            }
 +
            echo "</select>";
 +
            printf("</form>");
 +
            $result->free();  // Liberamos da memoria os recursos
 +
        }
 +
        else {
 +
            echo "Non hai datos que amosar!!!!";
 +
        }
 +
    ?>       
 +
    <?php       
 +
        // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
 +
        if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
 +
            echo "<div style='margin-top:40px'>";
 +
            $query=sprintf(CONSULTAR_EXEMPLARES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
 +
            $result=$conex->query($query);
 +
            if ($result && ($result->num_rows>0)){
 +
                echo "<table border='1'>";
 +
                echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
 +
                while($row = $result->fetch_assoc()) {
 +
                    echo '<tr>';
 +
                    printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%s</td>",
 +
                            htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"]),CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"]),
 +
                            htmlentities($row["prezo"]),htmlentities($row["desc_estado"]));
 +
                    echo '</tr>';
 +
                }
 +
                echo '</table>';
 +
                $result->free();  // Liberamos da memoria os recursos
 +
            }
 +
            else {
 +
                echo "Non hai datos que amosar!!!!";
 +
            }
 +
            echo "</div>";
 +
        }
 +
        // $conex->close();  // Non fai falla pechar a conexión xa que esta se pecha ao final do páxina web. Noutras páxinas pode ser necesario
 +
       
 +
    ?>       
 +
 +
    </body>   
 +
</html>
 +
 +
<?php
 +
}  // Pecha o try da parte superior da páxina
 +
catch (mysqli_sql_exception $e) {
 +
    die("Erro mysql: " . $e->getMessage());
 +
}
 +
catch (Expcetion $e) {
 +
    die("Erro xeral: " . $e->getMessage());
 +
}
 +
finally{
 +
  if(!empty($conex)){
 +
      $conex->close();
 +
  }
 +
}
 +
?>
 +
</syntaxhighlight>
 +
 +
 +
 +
* E como queremos que o usuario poida pechar a sesión, creamos a páxina que o fai:
 +
'''Arquivo UD4_Exemplo1_validacion_sair.php'''
 +
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
<?php
 +
session_start();
 +
 +
session_unset();
 +
session_destroy();
 +
setcookie(session_name(),'',0,'/');
 +
 +
header("Location:UD4_Exemplo1_Login_Formulario.php");
 +
 +
 
</syntaxhighlight>
 
</syntaxhighlight>
  
  
−
: Liña 2: '''Sempre debemos ter o session_start() ao comezo da páxina''' se imos traballar con variables de sesión.
 
−
: Liñas 14-17: En caso de coincidir o login/password (nunha situación real teríamos que facer unha consulta a unha base de datos) utilizamos unha variable de sesión de nome '''autenticado''' e damos un valor de 1. En caso contrario terá un valor de 0.
 
  
−
: Dita variable debemos usala en todas as páxinas onde sexa necesario que o usuario estea autenticado. Vexamos un exemplo na seguinte páxina.
+
<br />
 +
 
 +
===Carrito da compra===
 +
 
 +
* Imos modificar a pantalla UD4_consultar_exemplares_por_libro_con_validacion.php para que podamos engadir exemplares a un suposto carrito.
 +
 
 +
 
 +
[[Imagen:UD4 Validando 01.JPG|1000px|Engadindo as opcións para un carrito da compra]]
 +
 
 +
 
 +
 
 +
<br />
 +
:* Engado a seguridade csrf_token ao formulario no que se escolle o libro.
 +
:* Comprobo que o usuario esté autenticado.
 +
:* Engado un enlace á paxina UD4_Exemplo1_engadir_produto_carrito.php no que envío como datos o csrf_token, o id de libro, o id de exemplar e o título. Neste caso opto por un enlace, pero tamén se podería facer cun formulario, enviando en campos ocultos os datos anteriores. Envío o título para non ter que ir á base de datos para obter o nome e amosalo no carrito.
 +
:* Engado un enlace para poder ver o contido do carrito da compra na páxina UD4_Exemplo1_ver_carrito.php
 +
 
 +
 
  
−
<u>Nota:</u> Fixarse que despois da orde 'header' hai un exit para que o script non continúe executándose. Isto é así xa que o header non se enviará ao cliente ata que non remate o script. Dependerá de nos como programadores se queremos 'facer' algo máis antes de redirixir a páxina do navegador do cliente.
+
'''Arquivo UD4_consultar_exemplares_por_libro_con_validacion.php'''
−
 
+
::<syntaxhighlight lang="java" enclose="div" highlight="9-12, 15-20, 45-47,54,91-92" >
−
: Nome da páxina: '''UD4_Exemplo1_Validado.php'''
 
−
::<syntaxhighlight lang="java" line enclose="div" highlight="2,4-6" >
 
 
<?php
 
<?php
−
  session_start();
+
/* Lembrar quitar isto ao probar o scripts sen erros */
−
 
+
ini_set('display_errors', 1);
−
  if ($_SESSION['autenticado']==0) {
+
ini_set('display_startup_errors', 1);
−
         header('location:UD4_Exemplo1_Validacion_Formulario.php');
+
error_reporting(E_ALL);
−
   }
+
 
 +
session_start();
 +
 
 +
if(!isset($_SESSION['autenticado'])){
 +
    header("Location:UD4_Exemplo1_Login_Formulario.php");
 +
    exit();
 +
}
 +
 
 +
if (!empty($_POST['lstLibros'])) {  // Seleccionamos un libro
 +
    if ($_POST['csrf_token'] !== $_SESSION['csrf_token']){   // Se non vimos de premer a combo sae.csrf_token para evitar ataques
 +
        unset($_SESSION['csrf_token']);
 +
         header("location:UD4_Exemplo1_Login_Formulario.php");
 +
        exit();
 +
    }
 +
}
 +
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // Para evitar ataques csrf
 +
 
 +
if(!isset($_SESSION['autenticado'])){
 +
    header("Location:UD4_Exemplo1_Login_Formulario.php");
 +
    exit();
 +
}
 +
 
 +
require ('/var/www/confBD/bd.inc.php');
 +
 
 +
$titulo = "";  // Empregado polos exemplares para cando se engade ao carrito
 +
 
 +
try {
 +
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);   // Indico o nome da bd
 +
    $conex->set_charset('utf8mb4');
  
 
?>
 
?>
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
 
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
<!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" lang="es" xml:lang="es">
 
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
 
     <head>
 
     <head>
−
         <title>Usuario autenticado</title>
+
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 +
         <title>Operacións BD</title>
 
     </head>
 
     </head>
 +
 
     <body>
 
     <body>
−
         <h1>Se podes ver isto es un usario autenticado...</h1>
+
         <div style='float:right; margin:20px'>  
 +
            <a href='UD4_Exemplo1_validacion_sair.php'>Saír da aplicación</a>
 +
            <a href='UD4_Exemplo1_ver_carrito.php'>Ver carrito <?php if(!empty($_SESSION['carrito']))  printf("(%s)",count($_SESSION['carrito']));?> </a>
 +
        </div>
 +
    <?php       
 +
       
 +
        $result=$conex->query(CONSULTAR_LIBROS);
 +
        if ($result && $result->num_rows>0){
 +
            printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
 +
            printf("<input type='hidden' name='csrf_token' value=%s />",$_SESSION['csrf_token']);
 +
 
 +
            echo "<select name='lstLibros' onChange='this.form.submit();'>";
 +
            echo "<option value='-1'></option>";
 +
            while($row = $result->fetch_assoc()) {
 +
                $cadea = '';
 +
                if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){  // Se o libro seleccionado é igual ao id da fila, o seleccionamos en html
 +
                    $cadea="selected='selected'";
 +
                    $titulo = htmlentities($row["titulo"]);
 +
                }
 +
 
 +
                printf("<option value='%d' %s>%s</option>",htmlentities($row["id"]),$cadea,htmlentities($row["titulo"]));
 +
            }
 +
            echo "</select>";
 +
            printf("</form>");
 +
            $result->free();  // Liberamos da memoria os recursos
 +
        }
 +
        else {
 +
            echo "Non hai datos que amosar!!!!";
 +
        }
 +
    ?>       
 +
    <?php       
 +
        // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
 +
        if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
 +
 
 +
           
 +
            echo "<div style='margin-top:40px'>";
 +
            $query=sprintf(CONSULTAR_EXEMPLARES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
 +
            $result=$conex->query($query);
 +
            if ($result && ($result->num_rows>0)){
 +
                echo "<table border='1'>";
 +
                echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
 +
                while($row = $result->fetch_assoc()) {
 +
                    echo '<tr>';
 +
                    printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%s</td>",
 +
                            htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"]),CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"]),
 +
                            htmlentities($row["prezo"]),htmlentities($row["desc_estado"]));
 +
                    printf("<td><a href='UD4_Exemplo1_engadir_produto_carrito.php?id_libro=%s&id_exemplar=%s&titulo=%s&token=%s'>ADD CARRITO</a></td>",
 +
                        htmlentities($row["libro_id"]),htmlentities($row["id_exemplar"]),$titulo, $_SESSION['csrf_token']);
 +
                    echo '</tr>';
 +
                }
 +
                echo '</table>';
 +
                $result->free();  // Liberamos da memoria os recursos
 +
            }
 +
            else {
 +
                echo "Non hai datos que amosar!!!!";
 +
            }
 +
            echo "</div>";
 +
        }
 +
        // $conex->close();  // Non fai falla pechar a conexión xa que esta se pecha ao final do páxina web. Noutras páxinas pode ser necesario
 
          
 
          
−
        <a href="UD4_Exemplo1_Validacion_Formulario.php">Pulsa para volver a validarte...</a>
+
    ?>      
−
     </body>
+
 
 +
     </body>  
 
</html>
 
</html>
 +
 +
<?php
 +
}  // Pecha o try da parte superior da páxina
 +
catch (mysqli_sql_exception $e) {
 +
    die("Erro mysql: " . $e->getMessage());
 +
}
 +
catch (Expcetion $e) {
 +
    die("Erro xeral: " . $e->getMessage());
 +
}
 +
finally{
 +
  if(!empty($conex)){
 +
      $conex->close();
 +
  }
 +
}
 +
?>
 +
</syntaxhighlight>
 +
 +
 +
:* '''Liñas 9-12:''' Se o usuario non está autenticado, sae da pantalla.
 +
:* '''Liñas 15-20:''' Comprobo se al premer a combo de selección de libro, o csrf_token enviado coincide có xerado ao cargar a páxina por primeira vez
 +
:* '''Liñas 45-47:''' Engado os enlaces para saír e ver o carrito.
 +
:* '''Liña 54:''' Engado o token de seguridade.
 +
:* '''Liñas 91-92:''' Engado o enlace para engadir un produto ao carrito enviando a clave primaria de exemplares e o token de seguridade.
 +
 +
 +
<br />
 +
 +
'''Arquivo UD4_Exemplo1_engadir_produto_carrito.php'''
 +
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
<?php
 +
/* Lembrar quitar isto ao probar o scripts sen erros */
 +
ini_set('display_errors', 1);
 +
ini_set('display_startup_errors', 1);
 +
error_reporting(E_ALL);
 +
 +
 +
session_start();
 +
 +
 +
if(!isset($_SESSION['autenticado'])){
 +
    header("Location:UD4_Exemplo1_Login_Formulario.php");
 +
    exit();
 +
}
 +
 +
if ($_GET['token'] !== $_SESSION['csrf_token']){    // Se non vimos de premer o enlace volvemos. FIXARSE QUE SE EMPREGA $_GET
 +
    unset($_SESSION['csrf_token']);
 +
    header("location:UD4_Exemplo1_Login_Formulario.php");
 +
    exit();
 +
}
 +
unset($_SESSION['csrf_token']); // Para evitar ataques csrf
 +
 +
$id_libro = htmlentities($_GET['id_libro']);
 +
$id_exemplar = htmlentities($_GET['id_exemplar']);
 +
$titulolibro = htmlentities($_GET['titulo']);
 +
 +
if(!isset($_SESSION['carrito'])) $_SESSION['carrito'] = [];
 +
 +
$datos_asociativo= ['id_libro' => $id_libro,
 +
                    'id_exemplar' => $id_exemplar,
 +
                    'titulo' => $titulolibro
 +
                ];
 +
 +
foreach($_SESSION['carrito'] as $item){
 +
 +
    if($item==$datos_asociativo){
 +
        $_SESSION['error']="O exemplar xa está no carrito";    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
 +
        header("Location:UD4_consultar_exemplares_por_libro_con_validacion.php");
 +
        exit();
 +
    }
 +
}
 +
 +
$_SESSION['carrito'][] = $datos_asociativo;
 +
 +
$_SESSION['msg'] = "Producto engadido correctamente";
 +
 +
 +
header("Location:UD4_consultar_exemplares_por_libro_con_validacion.php");
 +
 +
?>     
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
: Liña 2: '''Sempre debemos ter o session_start() ao comezo da páxina''' se imos traballar con variables de sesión.
 
−
: Liñas 4-7: É onde facemos a validación do usuario. Se dito usuario non foi autenticado, entón irá a páxina inicial.
 
  
  
Línea 146: Línea 719:
  
  
−
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2016).
+
 
 +
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2023).

Revisión actual del 12:03 29 nov 2023

Introdución

  • Un aspecto ao que case sempre imos enfrontarnos é ao de permitir o acceso a certas páxinas en función dos permisos que teña un determinado cliente.
  • Para obter ditos permisos teremos que validar ao usuario e identificalo.
A identificación case sempre vai vir dun formulario no que o usuario introduce o seu login-password.
Nos recollemos ditos datos e normalmente faremos unha consulta a unha base de datos para comprobar que o usuario existe.
  • Neste punto imos ver como podemos facer para que o usuario non poida entrar nas páxinas se non está validado.
Fixarse que estamos implementando un mecanismo de autenticación de usuarios.
Non indicamos nada acerca dos permisos que ten dito usuario. Por exemplo, un administrador e un usuario son usuarios que teñen dereito de conexión ao sitio web, pero cada un deles terá permisos diferentes para poder realizar accións distintas...



  • IMPORTANTE: Durante o proceso de autenticación, o navegador envía ao servidor o usuario/password introducido polo usuario. Eses datos deben ir cifrados para que non poidan ser obtidos de forma fraudulenta pola rede. Para iso é necesario empregar certificados dixitais que no caso das páxinas web, tradúcese en empregar unha conexión https no que o servidor web emite un certificado dixital váliado que o indentifica.
Nunca se debe desenvolver unha aplicación web no que non se empregue este tipo de certificado.
Como facelo queda fora dos contidos deste curso, polo que as probas que estades a desenvolver empregan conexións non seguras de tipo http.

Exemplo rexistro/validación de usuario e carrito da compra

  • Imos facer un pequeno sitio web no que imos poder rexistar novos usuarios e despois mediante unha validación imos poder ter acceso as páxinas para mercar produtos e xestionar un carrito da compra




Rexistro de usuarios

  • Nesta páxina vaise amosar un login/password e vaise rexistrar un usuario/password.
Primeiro temos que crear a táboa usuarios no Mysql:
1 CREATE TABLE `php`.`usuarios` (
2   `id` INT NOT NULL AUTO_INCREMENT,
3   `login` VARCHAR(50) NOT NULL,
4   `password` VARCHAR(255) NOT NULL,
5   PRIMARY KEY (`id`),
6   UNIQUE INDEX `login_UNIQUE` (`login` ASC) VISIBLE);
Nesta táboa o password vai estar encriptado cunha función de hash que en principio devolve como mínimo 60 caracteres, pero que pode chegar a devolver ata 255 caracteres, por se nun futuro cambia a implementación.



  • Agora necesitamos gardar usuarios/password nesa táboa. Para iso imos facer uso das funcións:
  • Engadimos ao arquivo bd.inc.php que queda fora da ruta onde apunta o sitio web, a orde para engadir un usuario. Lembrar que isto xa o vimos na UD3.
Podemos empregar un procedemento almacenado de Mysql ou a orde SQL Insert. Lembrar que xa expliquei que é moito máis seguro empregar o procedemento ou ordes prepare.

Arquivo /var/www/confBD/bd.inc.php

1 define('INSERIR_USUARIOS',"INSERT INTO usuarios (login,password) VALUES ('%s','%s')");


  • Creamos unha páxina php para dar de alta usuarios.
Como queremos enviar a esta páxina información se o rexistro foi correcto ou se houbo algún tipo de erro, imos crear un include no que se amosarán o contido de dúas variables de sesión que imos empregar para isto:

Arquivo UD4_Exemplo1_Mensaxes.inc.php

        <?php
            if (!empty($_SESSION['msg'])){
                printf("<div>INFORMACIÓN: <b>%s</b>",$_SESSION['msg']);
            }
            if (!empty($_SESSION['error'])){
                printf("<div>ERROR: <b>%s</b>",$_SESSION['error']);
            }
            unset($_SESSION['msg']);
            unset($_SESSION['error']);




Arquivo UD4_Exemplo1_Rexistro_Formulario.php
<?php
    session_start();
    unset($_SESSION['autenticado']);
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // Para evitar ataques csrf
?>

<!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" lang="es" xml:lang="es">
    <head>
        <meta charset="utf-8" />
        <title>Formulario</title>
        <style>
            .formularios{
                width: 30%;
                margin: 0px auto;
            }
            .etiqueta{
                float: left;
                width: 190px;
            }
            .botonesformularios{
                width: 50%;
                margin: 30px auto;
                text-align: center;
            }
        </style>
    </head>
    <body>
        <form id="frmValidacion" class="formularios" method="post" action="procesar_UD4_Exemplo1_Rexistro_Usuarios.php">
            <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
            <div>    
                <div class='etiqueta'>Login:</div>
                <input type="text" required size="15" maxlength="15" name="txtLogin" />
            </div>                
            <div>
                <div class='etiqueta'>Password:</div>
                <input type="password" required size="15" maxlength="15" name="txtPassword" />
            </div>                
            <div class="botonesformularios">
                <input type='submit' value="ENVIAR"  name="btnRexistrar" />
                <input type='reset' value="LIMPAR" name="btnLimpar" />
            </div>
        </form>

        <?php 
        include('UD4_Exemplo1_Mensaxes.inc.php')
        ?>
    </body>    
</html>
Liña 2: Sempre debemos ter o session_start() ao comezo da páxina se imos traballar con variables de sesión.
Liña 3: Dita variable de sesión estará explicada posteriormente. Adiantar que é a variable que imos utilizar para deixar visualizar ao usuario as páxinas protexidas (que requiran unha autenticación previa).
Liña 47: Facemos o include das mensaxes de erro e aviso se se producen na páxina de procesar.


Visualizamos isto:

Php validacion 1.jpg




  • O seguinte será rexistrar o usuario/password na táboa usuarios.
Para iso imos facer uso da función password_hash() que permite encriptar un texto cun cifrado e vai ser a password a gardar na táboa de usuarios.
Para comprobar o usuario cando intente entrar empregaremos password_verify().



Arquivo procesar_UD4_Exemplo1_Rexistro_Usuarios.php
<?php
/* Lembrar quitar isto ao probar o scripts sen erros */
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);


session_start();

if (!isset($_POST['btnRexistrar']) || ($_POST['csrf_token'] !== $_SESSION['csrf_token'])){    // Se non vimos de premer o botón de rexistro volvemos a login. O csrf_token para evitar ataques
    unset($_SESSION['csrf_token']);
    header("location:UD4_Exemplo1_Rexistro_Formulario.php");
    exit();
}

require ('/var/www/confBD/bd.inc.php');

try {
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);   
    $conex->set_charset('utf8mb4');
    // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion
    if ((strlen($_POST['txtLogin'])<4) || (strlen($_POST['txtPassword'])<4)){
        $_SESSION['error'] = sprintf("A lonxitude do login e password non poden ter menos de 4 caracteres...");
    }
    else {
        // Eliminamos caracteres non permitidos
        $login = filter_var($_POST['txtLogin'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
        $password = password_hash($_POST['txtPassword'],PASSWORD_DEFAULT);

        // Escapamos as comillas simples e dobres
        $login = $conex->real_escape_string(strtoupper($login));
        $ordeSQL=sprintf(INSERIR_USUARIOS,$login,$password);
        $conex->query($ordeSQL);

        $_SESSION['msg'] = sprintf("Usuario rexistrado correctamente.");        
    }  
        
}   // Pecha o try da parte superior da páxina. Lembrar que aos usuarios non se lle deben amosar os erros. Isto está posto a nivel informativo para os programadores
catch (mysqli_sql_exception $e) {
    $_SESSION['error']=sprintf("Erro mysql: %s",htmlentities($e->getMessage()));    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
}   
catch (Expcetion $e) {
    $_SESSION['error']=sprintf("Erro xeral: %s",htmlentities($e->getMessage()));    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
}
finally{
    if(!empty($conex)){
        $conex->close();
    }
}

unset($_SESSION['csrf_token']); // Para evitar ataques csrf

$url = sprintf("UD4_Exemplo1_Rexistro_Formulario.php");
header("Location:" . $url);

?>


Liña 8: Fixarse que despois da orde 'header' hai un exit para que o script non continúe executándose. Isto é así xa que o header non se enviará ao cliente ata que non remate o script. Dependerá de nos como programadores se queremos 'facer' algo máis antes de redirixir a páxina do navegador do cliente.
Liña 13: Sempre debemos ter o session_start() ao comezo da páxina se imos traballar con variables de sesión.
Liña 28: Encriptamos o password.





Validando usuarios

  • Agora que xa podemos rexistrar usuarios imos crear un formulario de login.
O código será o mesmo que o de rexistro, cambiando o action do formulario (poderiamos poñer a mesma páxina cunha variable cambiando o action en función do que páxina se cargue).


Arquivo UD4_Exemplo1_Login_Formulario.php

 1 <?php
 2     session_start();
 3     unset($_SESSION['autenticado']);
 4     $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // Para evitar ataques csrf
 5 ?>
 6 
 7 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 8     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 9 <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
10     <head>
11         <meta charset="utf-8" />
12         <title>Formulario</title>
13         <style>
14             .formularios{
15                 width: 30%;
16                 margin: 0px auto;
17             }
18             .etiqueta{
19                 float: left;
20                 width: 190px;
21             }
22             .botonesformularios{
23                 width: 50%;
24                 margin: 30px auto;
25                 text-align: center;
26             }
27         </style>
28     </head>
29     <body>
30         <form id="frmValidacion" class="formularios" method="post" action="procesar_UD4_Exemplo1_Login_Usuarios.php">
31             <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
32             <div>    
33                 <div class='etiqueta'>Login:</div>
34                 <input type="text" required size="15" maxlength="15" name="txtLogin" />
35             </div>                
36             <div>
37                 <div class='etiqueta'>Password:</div>
38                 <input type="password" required size="15" maxlength="15" name="txtPassword" />
39             </div>                
40             <div class="botonesformularios">
41                 <input type='submit' value="ENVIAR"  name="btnRexistrar" />
42                 <input type='reset' value="LIMPAR" name="btnLimpar" />
43             </div>
44         </form>
45 
46         <?php 
47         include('UD4_Exemplo1_Mensaxes.inc.php')
48         ?>
49     </body>    
50 </html>


  • Agora necesitamos comprobar se o login-password está rexistrado.
Para iso creamos no arquivo bd.inc.php a orde SQL que vai comprobar isto.

Arquivo bd.inc.php

1 define('VALIDAR_USUARIOS', "SELECT password FROM usuarios WHERE login='%s'");   // O login non debería poder repetirse ao rexistrar un usuario


  • E por último, creamos a páxina que vai validar dito usuario. En caso de que exista e sexa correcto o login/password, empregaremos a variable de sessión $_SESSION['autenticado'] para darlle o valor do login e cargaremos unha páxina de nome consultar_exemplares_por_libro_con_validacion.php baseada nunha páxina feita na UD3.
A variable a empregaremos para chequear todas as páxinas nas que se necesite que o usuario estea autentificado.


Arquivo procesar_UD4_Exemplo1_Login_Usuarios.php

 1 <?php
 2 /* Lembrar quitar isto ao probar o scripts sen erros */
 3 ini_set('display_errors', 1);
 4 ini_set('display_startup_errors', 1);
 5 error_reporting(E_ALL);
 6 
 7 session_start();
 8 
 9 if (!isset($_POST['btnRexistrar']) || ($_POST['csrf_token'] !== $_SESSION['csrf_token'])){    // Se non vimos de premer o botón de rexistro volvemos a login. O csrf_token para evitar ataques
10     unset($_SESSION['csrf_token']);
11     header("location:UD4_Exemplo1_Login_Formulario.php");
12     exit();
13 }
14 unset($_SESSION['csrf_token']); // Para evitar ataques csrf
15 
16 require ('/var/www/confBD/bd.inc.php');
17 
18 try {
19     $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);   
20     $conex->set_charset('utf8mb4');
21     // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion
22     if ((strlen($_POST['txtLogin'])>50)){
23         $_SESSION['error'] = sprintf("A lonxitude do login non pode ser maior a 50 caracteres...");
24     }
25     else {
26         // Eliminamos caracteres non permitidos
27         $login = filter_var($_POST['txtLogin'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
28         $password = $_POST['txtPassword'];
29 
30         // Escapamos as comillas simples e dobres
31         $login = $conex->real_escape_string(strtoupper($login));
32         $ordeSQL=sprintf(VALIDAR_USUARIOS,$login,$password);
33         $result = $conex->query($ordeSQL);
34         if ($result && $result->num_rows>0){
35             $row = mysqli_fetch_assoc($result);
36             $result->free();  // Liberamos da memoria os recursos
37             if(password_verify($password, $row['password'])){    // Usuario/Password correcto
38                 $_SESSION['autenticado']=$login;    // Permite saber se está autenticado no resto de páxinas
39                 header("Location:UD4_consultar_exemplares_por_libro_con_validacion.php");
40                 exit();
41             }
42             else{
43                 $_SESSION['error']=sprintf("Usuario ou password non correctos");    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
44                 header("Location:UD4_Exemplo1_Login_Formulario.php");
45                 exit();
46             }
47         }
48         else{
49             $_SESSION['error']=sprintf("Usuario ou password non correctos");    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
50             header("Location:UD4_Exemplo1_Login_Formulario.php");
51             exit();
52         }
53 
54     }  
55         
56 }   // Pecha o try da parte superior da páxina. Lembrar que aos usuarios non se lle deben amosar os erros. Isto está posto a nivel informativo para os programadores
57 catch (mysqli_sql_exception $e) {
58     $_SESSION['error']=sprintf("Erro mysql: %s",htmlentities($e->getMessage()));    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
59 }   
60 catch (Expcetion $e) {
61     $_SESSION['error']=sprintf("Erro xeral: %s",htmlentities($e->getMessage()));    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
62 }
63 finally{
64     if(!empty($conex)){
65         $conex->close();
66     }
67 }
68 
69 unset($_SESSION['csrf_token']); // Para evitar ataques csrf
70 $url = sprintf("UD4_Exemplo1_Login_Formulario.php");
71 header("Location:" . $url);
72 
73 ?>



  • Agora soamente temos que verificar en todas as páxinas que necesiten autentificación a variable de sesión

Arquivo UD4_consultar_exemplares_por_libro_con_validacion.php

 1 <?php
 2 /* Lembrar quitar isto ao probar o scripts sen erros */
 3 ini_set('display_errors', 1);
 4 ini_set('display_startup_errors', 1);
 5 error_reporting(E_ALL);
 6 
 7 session_start();
 8 
 9 if(!isset($_SESSION['autenticado'])){
10     header("Location:UD4_Exemplo1_Login_Formulario.php");
11 }
12 
13 require ('/var/www/confBD/bd.inc.php');
14 
15 try {
16     $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);   // Indico o nome da bd
17     $conex->set_charset('utf8mb4');
18 
19 ?>
20 
21 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
22 <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
23     <head>
24         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
25         <title>Operacións BD</title>
26     </head>
27 
28     <body>
29         <div style='float:right; margin:20px'> 
30             <a href='UD4_Exemplo1_validacion_sair.php'>Saír da aplicación</a>
31         </div>
32     <?php        
33         
34         $result=$conex->query(CONSULTAR_LIBROS);
35         if ($result && $result->num_rows>0){
36             printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
37             echo "<select name='lstLibros' onChange='this.form.submit();'>";
38             echo "<option value='-1'></option>";
39             while($row = $result->fetch_assoc()) {
40                 $cadea = '';
41                 if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){  // Se o libro seleccionado é igual ao id da fila, o seleccionamos en html
42                     $cadea="selected='selected'";
43                 }
44 
45                 printf("<option value='%d' %s>%s</option>",htmlentities($row["id"]),$cadea,htmlentities($row["titulo"]));
46             }
47             echo "</select>";
48             printf("</form>");
49             $result->free();  // Liberamos da memoria os recursos
50         }
51         else {
52             echo "Non hai datos que amosar!!!!";
53         }
54     ?>        
55     <?php        
56         // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
57         if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
58             echo "<div style='margin-top:40px'>";
59             $query=sprintf(CONSULTAR_EXEMPLARES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
60             $result=$conex->query($query);
61             if ($result && ($result->num_rows>0)){
62                 echo "<table border='1'>";
63                 echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
64                 while($row = $result->fetch_assoc()) {
65                     echo '<tr>';
66                     printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%s</td>",
67                             htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"]),CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"]),
68                             htmlentities($row["prezo"]),htmlentities($row["desc_estado"]));
69                     echo '</tr>';
70                 }
71                 echo '</table>';
72                 $result->free();  // Liberamos da memoria os recursos
73             }
74             else {
75                 echo "Non hai datos que amosar!!!!";
76             }
77             echo "</div>";
78         }
79         // $conex->close();  // Non fai falla pechar a conexión xa que esta se pecha ao final do páxina web. Noutras páxinas pode ser necesario
80         
81     ?>        
82 
83     </body>    
84 </html>
85 
86 <?php
87 }   // Pecha o try da parte superior da páxina
88 catch (mysqli_sql_exception $e) {
89     die("Erro mysql: " . $e->getMessage());
90 }
91 catch (Expcetion $e) {
92     die("Erro xeral: " . $e->getMessage());
93 }
94 finally{
95   if(!empty($conex)){
96       $conex->close();
97   }
98 }
99 ?>


  • E como queremos que o usuario poida pechar a sesión, creamos a páxina que o fai:

Arquivo UD4_Exemplo1_validacion_sair.php

1 <?php
2 session_start();
3 
4 session_unset();
5 session_destroy();
6 setcookie(session_name(),'',0,'/');
7 
8 header("Location:UD4_Exemplo1_Login_Formulario.php");



Carrito da compra

  • Imos modificar a pantalla UD4_consultar_exemplares_por_libro_con_validacion.php para que podamos engadir exemplares a un suposto carrito.


Engadindo as opcións para un carrito da compra



  • Engado a seguridade csrf_token ao formulario no que se escolle o libro.
  • Comprobo que o usuario esté autenticado.
  • Engado un enlace á paxina UD4_Exemplo1_engadir_produto_carrito.php no que envío como datos o csrf_token, o id de libro, o id de exemplar e o título. Neste caso opto por un enlace, pero tamén se podería facer cun formulario, enviando en campos ocultos os datos anteriores. Envío o título para non ter que ir á base de datos para obter o nome e amosalo no carrito.
  • Engado un enlace para poder ver o contido do carrito da compra na páxina UD4_Exemplo1_ver_carrito.php


Arquivo UD4_consultar_exemplares_por_libro_con_validacion.php

<?php
/* Lembrar quitar isto ao probar o scripts sen erros */
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

session_start();

if(!isset($_SESSION['autenticado'])){
    header("Location:UD4_Exemplo1_Login_Formulario.php");
    exit();
}

if (!empty($_POST['lstLibros'])) {   // Seleccionamos un libro
    if ($_POST['csrf_token'] !== $_SESSION['csrf_token']){    // Se non vimos de premer a combo sae.csrf_token para evitar ataques
        unset($_SESSION['csrf_token']);
        header("location:UD4_Exemplo1_Login_Formulario.php");
        exit();
    }
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // Para evitar ataques csrf

if(!isset($_SESSION['autenticado'])){
    header("Location:UD4_Exemplo1_Login_Formulario.php");
    exit();
}

require ('/var/www/confBD/bd.inc.php');

$titulo = "";   // Empregado polos exemplares para cando se engade ao carrito

try {
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);   // Indico o nome da bd
    $conex->set_charset('utf8mb4');

?>

<!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" lang="es" xml:lang="es">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Operacións BD</title>
    </head>

    <body>
        <div style='float:right; margin:20px'> 
            <a href='UD4_Exemplo1_validacion_sair.php'>Saír da aplicación</a>
            <a href='UD4_Exemplo1_ver_carrito.php'>Ver carrito <?php if(!empty($_SESSION['carrito']))  printf("(%s)",count($_SESSION['carrito']));?> </a>
        </div>
    <?php        
        
        $result=$conex->query(CONSULTAR_LIBROS);
        if ($result && $result->num_rows>0){
            printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
            printf("<input type='hidden' name='csrf_token' value=%s />",$_SESSION['csrf_token']);

            echo "<select name='lstLibros' onChange='this.form.submit();'>";
            echo "<option value='-1'></option>";
            while($row = $result->fetch_assoc()) {
                $cadea = '';
                if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){  // Se o libro seleccionado é igual ao id da fila, o seleccionamos en html
                    $cadea="selected='selected'";
                    $titulo = htmlentities($row["titulo"]);
                }

                printf("<option value='%d' %s>%s</option>",htmlentities($row["id"]),$cadea,htmlentities($row["titulo"]));
            }
            echo "</select>";
            printf("</form>");
            $result->free();  // Liberamos da memoria os recursos
        }
        else {
            echo "Non hai datos que amosar!!!!";
        }
    ?>        
    <?php        
        // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
        if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){

            
            echo "<div style='margin-top:40px'>";
            $query=sprintf(CONSULTAR_EXEMPLARES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
            $result=$conex->query($query);
            if ($result && ($result->num_rows>0)){
                echo "<table border='1'>";
                echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
                while($row = $result->fetch_assoc()) {
                    echo '<tr>';
                    printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%s</td>",
                            htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"]),CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"]),
                            htmlentities($row["prezo"]),htmlentities($row["desc_estado"]));
                    printf("<td><a href='UD4_Exemplo1_engadir_produto_carrito.php?id_libro=%s&id_exemplar=%s&titulo=%s&token=%s'>ADD CARRITO</a></td>",
                        htmlentities($row["libro_id"]),htmlentities($row["id_exemplar"]),$titulo, $_SESSION['csrf_token']);
                    echo '</tr>';
                }
                echo '</table>';
                $result->free();  // Liberamos da memoria os recursos
            }
            else {
                echo "Non hai datos que amosar!!!!";
            }
            echo "</div>";
        }
        // $conex->close();  // Non fai falla pechar a conexión xa que esta se pecha ao final do páxina web. Noutras páxinas pode ser necesario
        
    ?>        

    </body>    
</html>

<?php
}   // Pecha o try da parte superior da páxina
catch (mysqli_sql_exception $e) {
    die("Erro mysql: " . $e->getMessage());
}
catch (Expcetion $e) {
    die("Erro xeral: " . $e->getMessage());
}
finally{
  if(!empty($conex)){
      $conex->close();
  }
}
?>


  • Liñas 9-12: Se o usuario non está autenticado, sae da pantalla.
  • Liñas 15-20: Comprobo se al premer a combo de selección de libro, o csrf_token enviado coincide có xerado ao cargar a páxina por primeira vez
  • Liñas 45-47: Engado os enlaces para saír e ver o carrito.
  • Liña 54: Engado o token de seguridade.
  • Liñas 91-92: Engado o enlace para engadir un produto ao carrito enviando a clave primaria de exemplares e o token de seguridade.



Arquivo UD4_Exemplo1_engadir_produto_carrito.php

 1 <?php
 2 /* Lembrar quitar isto ao probar o scripts sen erros */
 3 ini_set('display_errors', 1);
 4 ini_set('display_startup_errors', 1);
 5 error_reporting(E_ALL);
 6 
 7 
 8 session_start();
 9 
10 
11 if(!isset($_SESSION['autenticado'])){
12     header("Location:UD4_Exemplo1_Login_Formulario.php");
13     exit();
14 }
15 
16 if ($_GET['token'] !== $_SESSION['csrf_token']){    // Se non vimos de premer o enlace volvemos. FIXARSE QUE SE EMPREGA $_GET
17     unset($_SESSION['csrf_token']);
18     header("location:UD4_Exemplo1_Login_Formulario.php");
19     exit();
20 }
21 unset($_SESSION['csrf_token']); // Para evitar ataques csrf
22 
23 $id_libro = htmlentities($_GET['id_libro']);
24 $id_exemplar = htmlentities($_GET['id_exemplar']);
25 $titulolibro = htmlentities($_GET['titulo']);
26 
27 if(!isset($_SESSION['carrito'])) $_SESSION['carrito'] = [];
28 
29 $datos_asociativo= ['id_libro' => $id_libro,
30                     'id_exemplar' => $id_exemplar,
31                     'titulo' => $titulolibro
32                 ];
33 
34 foreach($_SESSION['carrito'] as $item){
35 
36     if($item==$datos_asociativo){
37         $_SESSION['error']="O exemplar xa está no carrito";    // Lembra que getMessage non se debe enviar ao cliente. Posto por motivos de depuración
38         header("Location:UD4_consultar_exemplares_por_libro_con_validacion.php");
39         exit();
40     }
41 }
42 
43 $_SESSION['carrito'][] = $datos_asociativo;
44 
45 $_SESSION['msg'] = "Producto engadido correctamente";
46 
47 
48 header("Location:UD4_consultar_exemplares_por_libro_con_validacion.php");
49 
50 ?>





-- Ángel D. Fernández González -- (2023).