Sesiones HTTP

# Introducción

Puesto que las aplicaciones HTTP no poseen un estado, las sesiones proveen un modo de almacenar información sobre el usuario a través de las diferentes peticiones. Laravel incluye una gran variedad de sistemas de sesiones a los que se accede a través de un API unificada. Soporte para sistemas populares como MemcachedRedis y bases de datos de serie.

Configuración

El archivo de configuración de la sesión se almacena en config/session.php. Asegúrese de revisar las opciones disponibles en este archivo. Por defecto, Laravel está configurado para usar el controlador de sesión file, que funcionará bien para muchas aplicaciones.

TEl la opción de configuración driver define el lugar donde se almacenará la información de la sesión para cada petición. Laravel incluye varios drivers:

  • file - sessions are stored in storage/framework/sessions.
  • cookie - sessions are stored in secure, encrypted cookies.
  • database - sessions are stored in a relational database.
  • memcached / redis - sessions are stored in one of these fast, cache based stores.
  • array - sessions are stored in a PHP array and will not be persisted.

El driver array se utiliza en testing y previene la persistencia de los datos de sesión.

Pre-requisitos del driver

Database

Al utilizar el driver database (base de datos), será necesario crear una tabla para almacenar los elementos de la sesión. A continuación se muestra el Schema para dicha tabla:

Schema::create('sessions', function ($table) {
    $table->string('id')->unique();
    $table->foreignId('user_id')->nullable();
    $table->string('ip_address', 45)->nullable();
    $table->text('user_agent')->nullable();
    $table->text('payload');
    $table->integer('last_activity');
});

Se puede utilizar el comando de Artisan session:table para generar esta migración:

php artisan session:table

php artisan migrate

Redis

Antes de usar las sesiones de Redis con Laravel, necesitarás instalar la extensión PhpRedis PHP vía PECL o instalar el predis/predis package (~1.0) via Composer. Para más información sobre la configuración de Redis, consulte la página de documentación de Laravel.

En el archivo de configuración de la session, la opción connection puede utilizarse para especificar qué conexión Redis utiliza la sesión.

# Usar la sesión

Recuperación de datos

Hay dos formas de trabajar con los datos de sesión en Laravel: el helper session y a través de una instancia Request. Primero, revisemos el acceso a la sesión a través de una instancia Request, la cual se puede incluir como sugerencia de tipo en el método del controlador. Recordar, las dependencias de los métodos de un controlador se inyectan automáticamente a través del service container:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function show(Request $request, $id)
    {
        $value = $request->session()->get('key');

        //
    }
}

Cuando se obtiene un valor de la sesión, se puede pasar un valor por defecto como segundo argumento al método get. Este valor por defecto se retornará si la clave especificada no existe en la sesión. También se puede pasar un Closure como valor por defecto al método get y si la clave solicitada no existe, se ejecutará y retornará su resultado:

$value = $request->session()->get('key', 'default');

$value = $request->session()->get('key', function () {
    return 'default';
});

El Helper Global Session

También se puede utilizar la función global de PHP session para obtener y almacenar datos en la sesión. Al llamar al helper session con una cadena como único argumento, retornará el valor de esa clave en la sesión. Si se le llama con una pareja de clave / valor, se almacenarán esos valores en la sesión:

Route::get('home', function () {
    // Retrieve a piece of data from the session...
    $value = session('key');

    // Specifying a default value...
    $value = session('key', 'default');

    // Store a piece of data in the session...
    session(['key' => 'value']);
});

Hay una pequeña diferencia práctica entre utilizar la sesión a través de una instancia Request HTTP y utilizar el helper session. Ambos métodos son testables a través del método assertSessionHas, el cual está disponible en todos los test cases.

Obtener todos los datos de la sesión

Para recuperar toda la información de la sesión, se puede utilizar el método all:

$data = $request->session()->all();

Determinar si un elemento existe en la sesión

Para determinar si un valor está presente en la sesión, se puede utilizar el método has. Este método retornará true si el valor está presente o null si no lo está:

if ($request->session()->has('users')) {
    //
}

Para determinar si un valor está presente en la sesión, incluso si el valor es null, se puede utilizar el método exists. El método exists retornará true si el valor está presente:

if ($request->session()->exists('users')) {
    //
}

Almacenamiento de datos

Para guardar datos en la sesión, se utiliza normalmente el método put o el helper session:

// Via a request instance...
$request->session()->put('key', 'value');

// Via the global helper...
session(['key' => 'value']);

Añadir datos a arrays en la sesión

El método push se utiliza para incluir un nuevo valor en un elemento de la sesión que sea un array. Por ejemplo, si la clave user.teams contiene un array de nombres, se puede añadir un nuevo valor al array así:

$request->session()->push('user.teams', 'developers');

Obtener & eliminar un elemento

El método pull obtendrá y eliminará un elemento de la sesión en un única declaración::

$value = $request->session()->pull('key', 'default');

Flash Data

A veces puede desear almacenar artículos en la sesión sólo para la próxima solicitud. Puede hacerlo utilizando el método flash. Los datos almacenados en la sesión mediante este método estarán disponibles inmediatamente y durante la siguiente solicitud HTTP. Después de la subsiguiente solicitud HTTP, los datos flasheados serán eliminados. Los datos flasheados son principalmente útiles para los mensajes de estado de corta duración:

$request->session()->flash('status', 'Task was successful!');

Si necesita mantener los datos del flash para varias solicitudes, puede utilizar el método reflash, que mantendrá todos los datos del flash para una solicitud adicional. Si sólo necesita mantener datos específicos sobre el flash, puede utilizar el método keep:

$request->session()->reflash();

$request->session()->keep(['username', 'email']);

Eliminación de datos

El método forget eliminará una porción de información de la sesión. Si desea borrar toda la información, se debe utilizar el método flush:

// Forget a single key...
$request->session()->forget('key');

// Forget multiple keys...
$request->session()->forget(['key1', 'key2']);

$request->session()->flush();

Regenerar el ID de Sesion

Regenerar el ID de sesión es una tarea que se suele llevar a cabo para prevenir ataques de fijación de sesión por parte de usuarios maliciosos en la aplicación.

Laravel regenera automáticamente el ID de la sesión durante la autenticación si está usando Laravel Jetstream; Sin embargo, si necesita regenerar manualmente el ID de la sesión, puede utilizar el método regenerate.

$request->session()->regenerate();

# Bloqueo de sesión

Para utilizar el bloqueo de sesión, su aplicación debe utilizar un controlador de caché que admita Bloqueos atómicos. Actualmente, esos controladores de caché incluyen los controladores memcached, dynamodb, redis, y database. Además, no puede utilizar el controlador de sesión cookie.

Por defecto, Laravel permite que las solicitudes que utilizan la misma sesión se ejecuten simultáneamente. Así, por ejemplo, si usas una librería HTTP de JavaScript para hacer dos peticiones HTTP a tu aplicación, ambas se ejecutarán al mismo tiempo. Para muchas aplicaciones, esto no es un problema; sin embargo, la pérdida de datos de sesión puede ocurrir en un pequeño subconjunto de aplicaciones que hacen peticiones simultáneas a dos puntos finales de aplicación diferentes, los cuales escriben ambos datos en la sesión.

Para mitigar esto, Laravel proporciona una funcionalidad que permite limitar las solicitudes simultáneas para una sesión determinada. Para empezar, puede simplemente encadenar el método de "block" en su definición de ruta. En este ejemplo, una solicitud entrante al punto final "/profile" adquiriría un bloqueo de sesión. Mientras se mantiene este bloqueo, cualquier solicitud entrante a los puntos finales "/profile" o "/order" que compartan el mismo ID de sesión esperará a que la primera solicitud termine de ejecutarse antes de continuar su ejecución:

Route::post('/profile', function () {
    //
})->block($lockSeconds = 10, $waitSeconds = 10)

Route::post('/order', function () {
    //
})->block($lockSeconds = 10, $waitSeconds = 10)

El método "block" acepta dos argumentos opcionales. El primer argumento aceptado por el método "block" es el número máximo de segundos que el bloqueo de la sesión debe mantenerse antes de ser liberado. Por supuesto, si la petición termina de ejecutarse antes de este tiempo el bloqueo se liberará antes.

El segundo argumento aceptado por el método block es el número de segundos que una solicitud debe esperar mientras intenta obtener un bloqueo de sesión. Se lanzará un Illuminate\Contracts\Cache\LockTimeoutException si la solicitud no puede obtener un bloqueo de la sesión en el número de segundos dado.

Si no se pasa ninguno de estos argumentos, el bloqueo se obtendrá durante un máximo de 10 segundos y las solicitudes esperarán un máximo de 10 segundos mientras intentan obtener un bloqueo:

Route::post('/profile', function () {
    //
})->block()

# Añadir drivers de sesión personalizados

Implementar el dirver

Un driver de sesión personalizado debe implementar SessionHanldlerInterface. La interfaz contiene unos pocos métodos que se deben implementar. Un ejemplo de una implementación para MongoDB sería algo así:

<?php

namespace App\Extensions;

class MongoSessionHandler implements \SessionHandlerInterface
{
    public function open($savePath, $sessionName) {}
    public function close() {}
    public function read($sessionId) {}
    public function write($sessionId, $data) {}
    public function destroy($sessionId) {}
    public function gc($lifetime) {}
}

Laravel no incluye ningún directorio para almacenar extensiones. Se pueden establecer donde convengan. En este ejemplo, se ha creado un directorio Extensiones para alojar el MongoSessionHandler.

Puesto que el propósito de estos métodos no es intuitivo, se van a revisar a continuación:

  • El método open se puede utilizar en sistemas de almacenamiento de sesión basados en archivos. Puesto que Laravel incluye el controlador de sesión file, casi nunca será necesario poner nada en este método. Se puede dejar vacío. Es solo un hecho sobre el pobre diseño de interfaz (del cual discutiremos luego) que PHP requiere para implementar este método.
  • El método close, como el open, puede no tenerse en cuenta. Para muchos de los controladores, este no es necesario.
  • El método read debe retornar la versión de la cadena de la información de sesión asociada con el $sessionId dado. No es necesario hacer ninguna serializacion u otra codificación cuando recupera o almacena información de sesión, ya que Laravel se encargará de esto automáticamente.
  • El método write debe escribir la cadena $data asociada con el $sessionId en algún sistema de almacenamiento persistente, como MongoDB, Dynamo, etc. De nuevo, no se debe realizar ninguna serialización – Laravel lo gestionará por nosotros.
  • El método destroy debe eliminar la información asociada con el $sessionId del almacenamiento.
  • El método gc debe destruir toda la información de sesión que sea anterior al $lifetime dado, el cual es un timestamp de UNIX. Para sistemas auto-expirables, como Memcached y Redis, este método debe dejarse vacío.

Registrar el driver

Una vez que se ha implementado el driver, ya se puede registrar en el framework. Para añadir drivers de sesión adicionales a Laravel, se puede utilizar el método extend de la Session facade. Se debe llamar al método extend desde el método boot de un service provider. Se podría hacer desde el AppServiceProvider existente o crear uno nuevo:

<?php

namespace App\Providers;

use App\Extensions\MongoSessionHandler;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;

class SessionServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Session::extend('mongo', function ($app) {
            // Return implementation of SessionHandlerInterface...
            return new MongoSessionHandler;
        });
    }
}

Una vez que el driver de sesión se ha registrado, se puede utilizar el driver mongo del archivo de configuración config/session.php.