PHP para principiantes. La sesión

ElePHPant. PHP para principiantes. Sesión

Que tengas un buen día. Aquí está el primer artículo de la serie PHP para desarrolladores principiantes. Esta será una serie inusual de artículos, no habrá echo "Hello World" , habrá hardcore de la vida de los programadores de PHP con una pequeña mezcla de "tarea" para consolidar el material.

Comenzaré con sesiones: este es uno de los componentes más importantes con los que tiene que trabajar. Sin entender los principios de su trabajo, hacer negocios. Entonces, para evitar problemas, trataré de hablar sobre todos los matices posibles.

Pero para empezar, para comprender por qué necesitamos una sesión, nos dirigimos a los orígenes, al protocolo HTTP.

Protocolo HTTP


El protocolo HTTP es el Protocolo de transferencia de hipertexto, el "Protocolo de transferencia de hipertexto", es decir de hecho - un protocolo de texto, y entiendo que no es difícil.
Inicialmente, se entendió que bajo este protocolo solo se transmitirá HTML, la dirección y el nombre, pero ahora simplemente no se enviarán y = ^. ^ = Y (• _ ㅅ _ •)

Para no andar por las ramas, déjame darte un ejemplo de comunicación a través del protocolo HTTP.
Aquí hay un ejemplo de la solicitud de envío de su navegador cuando solicita la página http://example.com :

 GET / HTTP/1.1 Host: example.com Accept: text/html < > 

Y aquí hay un ejemplo de respuesta:

 HTTP/1.1 200 OK Content-Length: 1983 Content-Type: text/html; charset=utf-8 <html> <head>...</head> <body>...</body> </html> 

Estos son ejemplos muy simplificados, pero incluso aquí puede ver en qué consisten la solicitud y la respuesta HTTP:

  1. línea de inicio - para una solicitud contiene el método y la ruta de la página solicitada, para una respuesta - la versión del protocolo y el código de respuesta
  2. encabezados : tienen un formato de clave-valor separado por dos puntos, cada nuevo encabezado se escribe desde una nueva línea
  3. cuerpo del mensaje : ya sea directamente HTML o los datos están separados de los encabezados por dos saltos de línea, puede estar ausente, como en la solicitud anterior

Entonces, de alguna manera descubrimos el protocolo: es simple, ha estado liderando su historia desde 1992, por lo que no lo nombrará ideal, pero lo que es, envíe una solicitud, obtenga una respuesta, y eso es todo, el servidor y el cliente ya no están conectados. Pero tal escenario no es el único posible, podemos tener autorización, el servidor debe entender de alguna manera que esta solicitud proviene de un usuario específico, es decir el cliente y el servidor deben comunicarse dentro de una determinada sesión. Y sí, se inventó el siguiente mecanismo para esto:

  1. Cuando un usuario autoriza, el servidor genera y recuerda una clave única: el identificador de sesión, y lo informa al navegador
  2. El navegador guarda esta clave y, con cada solicitud posterior, envía

Para implementar este mecanismo, se crearon cookies (cookies, cookies): archivos de texto simples en su computadora, por archivo para cada dominio (aunque algunos navegadores son más avanzados y usan una base de datos SQLite para almacenar), mientras que el navegador impone un límite en la cantidad de registros y el tamaño de los datos almacenados (para la mayoría de los navegadores esto es 4096 bytes, ver RFC 2109 de 1997)
Es decir Si robas una cookie de tu navegador, ¿puedes ir a tu página de Facebook en tu nombre? No se alarme, esto no se puede hacer, al menos con Facebook, y luego le mostraré una de las posibles formas de protegerse contra este tipo de ataque a sus usuarios.

Ahora veamos cómo cambia nuestra solicitud-respuesta, esté allí la autorización:

Solicitud
 POST /login/ HTTP/1.1 Host: example.com Accept: text/html login=Username&password=Userpass 


Nuestro método ha cambiado a POST, y el nombre de usuario y la contraseña se transmiten en el cuerpo de la solicitud. Si utiliza el método GET, la cadena de consulta contendrá un nombre de usuario y una contraseña, lo cual no es muy correcto desde un punto de vista ideológico, y tiene una serie de efectos secundarios en forma de registro (por ejemplo, en el mismo access.log ) y el almacenamiento en caché de contraseñas en forma clara.

Respuesta
 HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Set-Cookie: KEY=VerySecretUniqueKey <html> <head>...</head> <body>...</body> </html> 

La respuesta del servidor contendrá el encabezado Set-Cookie: KEY=VerySecretUniqueKey , lo que obligará al navegador a guardar estos datos en cookies, y la próxima vez que acceda al servidor, será enviado y reconocido por el servidor:

Solicitud
 GET / HTTP/1.1 Host: example.com Accept: text/html Cookie: KEY=VerySecretUniqueKey < > 

Como puede ver, los encabezados enviados por el navegador (encabezados de solicitud) y el servidor (encabezados de respuesta) son diferentes, aunque son comunes tanto para las solicitudes como para las respuestas (encabezados generales)

El servidor reconoció a nuestro usuario por la cookie enviada y además le proporcionará acceso a información personal. Entonces, bueno, con sesiones y HTTP resueltos, ahora puede volver a PHP y sus características.

PHP y sesión


Espero que ya tengas PHP instalado en tu computadora, como Además, daré ejemplos, y deberán ejecutarse

El lenguaje PHP fue creado para que coincida con el protocolo HTTP, es decir Su tarea principal es dar una respuesta a la solicitud HTTP y "morir" liberando memoria y recursos. Por lo tanto, el mecanismo de sesión no funciona en PHP en modo automático, sino en modo manual, y necesita saber a qué llamar y en qué orden.
Aquí tiene un artículo sobre el tema que PHP está destinado a morir , o aquí está en ruso , pero es mejor ponerlo en los marcadores "para más adelante".

En primer lugar, debe "iniciar" la sesión; para esto utilizamos la función session_start () , cree un archivo session.start.php con el siguiente contenido:

 <?php session_start(); 

Ejecute el servidor web PHP integrado en la carpeta con su script:

 php -S 127.0.0.1:8080 

Inicie el navegador y abra las Herramientas para desarrolladores (o lo que sea ), luego vaya a la página http://127.0.0.1:8080/session.start.php ; debería ver solo una página en blanco, pero no se apresure a cerrarla. a los encabezados que nos envió el servidor:

Galleta

Habrá muchas cosas, solo estamos interesados ​​en esta línea en la respuesta del servidor (limpia las cookies, si no hay esa línea, y actualiza la página):

 Set-Cookie: PHPSESSID=dap83arr6r3b56e0q7t5i0qf91; path=/ 

Al ver esto, el navegador guardará una cookie llamada `PHPSESSID`:

Cookie de sesión del navegador

PHPSESSID : el nombre de sesión predeterminado, se ajusta desde la configuración de php.ini con la directiva session.name , si es necesario, el nombre se puede cambiar en el archivo de configuración o utilizando la función session_name ()

Y ahora: actualizamos la página y vemos que el navegador envía esta cookie al servidor, puede intentar actualizar la página un par de veces, el resultado será idéntico:

Solicitud de navegador con cookie

El total que tenemos: la teoría coincidió con la práctica, y esto está bien.

El siguiente paso es guardar un valor arbitrario en la sesión, para esto, la variable súper global $_SESSION usa en PHP, $_SESSION la hora actual; para hacerlo, llame a la función date () :

 session_start(); $_SESSION['time'] = date("H:i:s"); echo $_SESSION['time']; 

Actualizamos la página y vemos la hora del servidor, la actualizamos nuevamente, y la hora se ha actualizado. Ahora asegurémonos de que el tiempo establecido no cambie con cada actualización de página:

 session_start(); if (!isset($_SESSION['time'])) { $_SESSION['time'] = date("H:i:s"); } echo $_SESSION['time']; 

Actualizamos: el tiempo no cambia, lo que se necesita. Pero al mismo tiempo, recordamos que PHP está muriendo, lo que significa que almacena esta sesión en algún lugar, y encontraremos este lugar ...

Todo secreto queda claro


De forma predeterminada, PHP almacena la sesión en archivos; la directiva session.save_handler es responsable de esto, busca la ruta a lo largo de la cual se guardan los archivos en la directiva session.save_path o usa la función session_save_path () para obtener la ruta necesaria.
En su configuración, es posible que no se especifique la ruta a los archivos, luego los archivos de sesión se almacenarán en archivos temporales de su sistema; llame a la función sys_get_temp_dir () y descubra dónde está este lugar oculto.

Entonces, seguimos este camino y encontramos su archivo de sesión (tengo este archivo sess_dap83arr6r3b56e0q7t5i0qf91 ), lo abrimos en un editor de texto:

 time|s:8:"16:19:51"; 

Como puede ver, aquí es nuestro momento, este es el formato complicado en el que se almacena nuestra sesión, pero podemos hacer cambios, cambiar el tiempo o simplemente podemos ingresar cualquier línea, por qué no:

 time|s:13:"\m/ (@.@) \m/"; 

Para convertir esta cadena en una matriz, debe usar la función session_decode () , para la conversión inversa - session_encode () - esto se llama serialización, solo en PHP para sesiones - es su propio - especial, aunque puede usar la serialización PHP estándar - escriba en la directiva de configuración de sesión El valor de .serialize_handler es php_serialize y estará contento, y $_SESSION puede usarse sin restricciones; ahora puede usar números y caracteres especiales como índice | y ! en el nombre (para los más de 10 años de trabajo, nunca tuve que :)

Tarea
Escriba su función, similar en función a session_decode() , aquí tiene un conjunto de datos de prueba para la sesión (no es necesario para resolver el conocimiento de expresiones regulares), tome el texto para la conversión del archivo de su sesión actual:

 $_SESSION['integer var'] = 123; $_SESSION['float var'] = 1.23; $_SESSION['octal var'] = 0x123; $_SESSION['string var'] = "Hello world"; $_SESSION['array var'] = array('one', 'two', [1,2,3]); $object = new stdClass(); $object->foo = 'bar'; $object->arr = array('hello', 'world'); $_SESSION['object var'] = $object; $_SESSION['integer again'] = 42; 


Entonces, ¿qué no hemos probado todavía? Así es: para robar cookies, iniciemos otro navegador y agreguemos las mismas cookies. Para esto escribí un javascript simple para usted, cópielo en la consola del navegador y ejecútelo, solo recuerde cambiar el identificador de sesión a su cuenta:

 javascript:(function(){document.cookie='PHPSESSID=dap83arr6r3b56e0q7t5i0qf91;path=/;';window.location.reload();})() 

Ahora ambos navegadores miran la misma sesión. Mencioné anteriormente que hablaré sobre métodos de protección, consideremos la forma más simple: vincularemos la sesión al navegador, más precisamente, a cómo se ve el navegador en el servidor; recordaremos al Agente de usuario y lo comprobaremos cada vez:

 session_start(); if (!isset($_SESSION['time'])) { $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT']; $_SESSION['time'] = date("H:i:s"); } if ($_SESSION['ua'] != $_SERVER['HTTP_USER_AGENT']) { die('Wrong browser'); } echo $_SESSION['time']; 

Es más difícil fingir, pero aún es posible, agregue el ahorro y la comprobación $_SERVER['REMOTE_ADDR'] y $_SERVER['HTTP_X_FORWARDED_FOR'] , y esto se verá más o menos como protección contra intrusos que invaden nuestras cookies.

La palabra clave en el párrafo anterior parece que las cookies se han estado ejecutando sobre el protocolo HTTPS en proyectos reales durante mucho tiempo, por lo que nadie puede robarlas sin acceso físico a su computadora o teléfono inteligente


Vale la pena mencionar la directiva session.cookie-httponly , gracias a ella la cookie de sesión será inaccesible desde JavaScript. Además, si mira el manual de funciones setcookie () , notará que el último parámetro también es responsable de HttpOnly. Recuerde esto: esta configuración le permite lidiar efectivamente con ataques XSS en casi todos los navegadores .

Tarea
Agregue un cheque a la IP del usuario en el código; si el cheque falla, elimine la sesión comprometida.

Paso a paso


Y ahora explicaré en pasos el algoritmo de cómo funciona una sesión en PHP, usando el siguiente código como ejemplo (configuración predeterminada):

 session_start(); $_SESSION['id'] = 42; 

  1. después de llamar a session_start() PHP busca en la cookie el identificador de sesión por el nombre especificado en session.name - esto es PHPSESSID
  2. si no hay un identificador, se crea (consulte session_id () ) y crea un archivo de sesión vacío a lo largo de la ruta session.save_path con el nombre sess_{session_id()} , se agregarán encabezados a la respuesta del servidor para configurar la cookie {session_name()}={session_id()}
  3. si el identificador está presente, busque el archivo de sesión en la carpeta session.save_path :
    • no lo encontramos: creamos un archivo vacío con el nombre sess_{$_COOKIE[session_name()]} (el identificador puede contener solo caracteres de los rangos az , AZ , 0-9 , una coma y un signo menos)
    • encuentre, lea el archivo y desempaquete los datos (vea session_decode () ) en la variable súper global $_SESSION (el archivo está bloqueado para lectura / escritura)
  4. cuando el script ha finalizado su trabajo, todos los datos de $_SESSION empaquetan usando session_encode() en un archivo a lo largo de la ruta sess_{session_id()} llamado sess_{session_id()} (se libera el bloqueo)

Tarea
Configure en su navegador un valor de cookie arbitrario con el nombre PHPSESSID , deje que sea 1234567890 , actualice la página, verifique que haya creado un nuevo archivo sess_1234567890

¿Hay vida sin galletas?


PHP puede funcionar con la sesión incluso si las cookies están deshabilitadas en el navegador, pero todas las URL del sitio contendrán un parámetro con el identificador de su sesión, y sí, ¿aún necesita configurar esto, pero lo necesita? No tuve que usarlo, pero si realmente quiero, solo diré dónde cavar:


¿Y si necesita almacenar una sesión en una base de datos?


Para almacenar una sesión en la base de datos, deberá cambiar el almacén de la sesión y decirle a PHP cómo usarla. Para este propósito, se han creado la interfaz SessionHandlerInterface y la función session_set_save_handler .
Por separado, noto que no necesita escribir sus propios controladores de sesión para redis y memcache: cuando instala estas extensiones, los controladores correspondientes también van con ellas, por lo que RTFM lo es todo. Bueno y sí, el controlador debe especificarse antes de llamar a session_start() ;)

Tarea
Implemente SessionHandlerInterface para almacenar la sesión en MySQL, verifique si funciona.
Esta es una tarea de asterisco para aquellos que ya se han familiarizado con las bases de datos.


¿Cuándo muere la sesión?


La directiva session.gc_maxlifetime es responsable de la duración de una sesión. Por defecto, esta directiva es igual a 1440 segundos (24 minutos), debe entenderse de modo que si no se ha accedido a la sesión durante un tiempo específico, la sesión se considerará "podrida" y esperará su turno para eliminarla.

Otra pregunta es interesante, ¿puede preguntarle a los desarrolladores maduros? ¿Cuándo elimina PHP los archivos de las sesiones caducadas? La respuesta está en la guía oficial, pero no explícitamente, así que recuerde:

La recolección de basura se puede iniciar cuando se llama a la función session_start() , la probabilidad de inicio depende de dos directivas session.gc_probability y session.gc_divisor , la primera actúa como un dividendo, la segunda actúa como un divisor y, por defecto, estos valores son 1 y 100, etc. e. La probabilidad de que se inicie el recopilador y se eliminen los archivos de sesión es aproximadamente del 1%.

Tarea
Cambie el valor de la directiva session.gc_divisor para que el recolector de basura se inicie cada vez, verifique que esto suceda.


El error mas trivial


Un error con más de medio millón de resultados en los resultados de Google:

No se puede enviar la cookie de sesión: los encabezados ya enviados por
No se puede enviar el limitador de caché de sesión: los encabezados ya se enviaron

Para obtener uno, cree un archivo session.error.php con el siguiente contenido:

 echo str_pad(' ', ini_get('output_buffering')); session_start(); 

En la segunda línea, una extraña "magia" es un foco con un búfer de salida, hablaré de ello en uno de los siguientes artículos, hasta ahora considero que esto es solo una cadena con una longitud de 4096 caracteres, en este caso son todos espacios

Comience eliminando la cookie de antemano y obtendrá los errores anteriores, aunque el texto del error es diferente, pero la esencia es la misma: el tren se fue: el servidor ya ha enviado el contenido de la página al navegador y es demasiado tarde para enviar los encabezados, esto no funcionará en las cookies y el identificador de sesión apreciado no aparece en las cookies. Si se encuentra con este error, busque un lugar donde el texto se muestre antes de tiempo, puede ser un espacio antes de los caracteres <?php o after ?> En uno de los archivos conectados, y bueno, si es un espacio, puede haber algún hilo que no sea imprimible como BOM , así que ten cuidado, y esta infección no te afectará (después de todo ... risas homéricas).

Tarea
Para probar este conocimiento, quiero que implemente su propio mecanismo de sesión y haga que el código anterior funcione:

 require_once 'include/sess.php'; sess_start(); if (isset($_SESS["id"])) { echo $_SESS["id"]; } else { $_SESS["id"] = 42; } 

Para implementar su plan, necesitará la función register_shutdown_function ()



Cerradura


Otro error común entre los principiantes es un intento de leer el archivo de sesión mientras está bloqueado por otro script. En realidad, esto no es un error, es un malentendido del principio de bloqueo :)

Pero volvamos a seguir los pasos:

  1. session_start() no solo crea / lee un archivo, sino que también lo bloquea para que nadie pueda hacer cambios en el momento en que se ejecuta el script o leer datos no consistentes del archivo de sesión
  2. el bloqueo se libera al final del guión


"Pegarse" en este error es muy fácil, cree dos archivos:

 // start.php session_start(); echo "OK"; 


 // lock.php session_start(); sleep(10); echo "OK"; 


Ahora, si abre la página lock.php en el navegador y luego abre start.php en una nueva pestaña, verá que la segunda página solo se abrirá después de que se haya ejecutado el primer script, que bloquea el archivo de sesión durante 10 segundos.

Hay un par de opciones para evitar este fenómeno: "torpe" y "reflexivo".

"Hacha"
Utilice un controlador de sesión personalizado en el que "olvidar" para implementar el bloqueo :)
Una opción un poco mejor es tomar una lista y deshabilitar el bloqueo (por ejemplo, memcached tiene esa opción: memcached.sess_locking ) O_o
Pase horas depurando el código en busca de un error emergente raro ...

"Pensativo"
¿Cuál es la mejor manera? Para controlar la sesión, bloquee usted mismo y elimínela cuando no sea necesario:

- Si está seguro de que no necesita realizar cambios en los datos de la sesión, use la opción read_and_close al iniciar la sesión:

 session_start([ 'read_and_close' => true ]); 


Por lo tanto, el bloqueo se liberará inmediatamente después de leer los datos de la sesión.

- Si aún necesita realizar cambios en la sesión, luego de hacerlos cerrar la sesión desde la grabación:

 session_start(); // some changes session_write_close(); 


Tarea
La lista de los dos archivos start.php y lock.php fue un poco más alta. Cree más archivos read-close.php y write-close.php , en los que controlará el bloqueo de las formas enumeradas. Compruebe cómo funciona el bloqueo (o no funciona).


En conclusión


En este artículo, se le han asignado siete tareas, mientras que se refieren no solo al trabajo con sesiones , sino también a presentarle MySQL y funciones de cadena . Para la asimilación de este material, no necesita un artículo separado, solo el manual en los enlaces provistos es suficiente, nadie lo leerá por usted. ¡A por ello!

PD: si aprendiste algo nuevo del artículo, gracias al autor, compártelo en las redes sociales;)
PPS Sí, este es un artículo cruzado de mi blog , pero sigue siendo relevante :)

Una serie de artículos "PHP para principiantes":

Source: https://habr.com/ru/post/437972/


All Articles