Saltar a contenido

session: Biblioteca de sesiones para nginx-module-lua – flexible y segura

Instalación

Si aún no has configurado la suscripción al repositorio RPM, regístrate. Luego, puedes proceder con los siguientes pasos.

CentOS/RHEL 7 o Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-session

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-session

Para usar esta biblioteca Lua con NGINX, asegúrate de que nginx-module-lua esté instalado.

Este documento describe lua-resty-session v4.1.5 lanzado el 24 de noviembre de 2025.


lua-resty-session es una biblioteca de sesiones segura y flexible para OpenResty.

TL;DR;

  • Las sesiones son inmutables (cada guardado genera una nueva sesión) y sin bloqueo.
  • Los datos de la sesión están cifrados con AES-256-GCM con una clave derivada usando HKDF-SHA256 (en modo FIPS utiliza PBKDF2 con SHA-256 en su lugar).
  • La sesión tiene un encabezado de tamaño fijo que está protegido con HMAC-SHA256 MAC con una clave derivada usando HKDF-SHA256 (en modo FIPS utiliza PBKDF2 con SHA-256 en su lugar).
  • Los datos de la sesión pueden almacenarse en una cookie sin estado o en varios almacenes de backend.
  • Una sola cookie de sesión puede mantener múltiples sesiones a través de diferentes audiencias.

Nota: La versión 4.0.0 fue una reescritura de esta biblioteca con muchas lecciones aprendidas a lo largo de los años. Si aún usas una versión anterior, consulta la documentación antigua.

Sinopsis

worker_processes  1;

events {
  worker_connections 1024;
}

http {
  init_by_lua_block {
    require "resty.session".init({
      remember = true,
      audience = "demo",
      secret   = "RaJKp8UQW1",
      storage  = "cookie",
    })
  }

  server {
    listen       8080;
    server_name  localhost;
    default_type text/html;

    location / {
      content_by_lua_block {
        ngx.say([[
          <html>
          <body>
            <a href=/start>Iniciar la prueba</a>
          </body>
          </html>
        ]])
      }
    }

    location /start {
      content_by_lua_block {
        local session = require "resty.session".new()
        session:set_subject("Fan de OpenResty")
        session:set("quote", "El rápido zorro marrón salta sobre el perro perezoso")
        local ok, err = session:save()

        ngx.say(string.format([[
          <html>
          <body>
            <p>Sesión iniciada (%s)</p>
            <p><a href=/started>Ver si realmente fue</a></p>
          </body>
          </html>
        ]], err or "sin error"))
      }
    }

    location /started {
      content_by_lua_block {
        local session, err = require "resty.session".start()

        ngx.say(string.format([[
          <html>
          <body>
            <p>La sesión fue iniciada por %s (%s)</p>
            <p><blockquote>%s</blockquote></p>
            <p><a href=/modify>Modificar la sesión</a></p>
          </body>
          </html>
        ]],
          session:get_subject() or "Anónimo",
          err or "sin error",
          session:get("quote") or "sin cita"
        ))
      }
    }

    location /modify {
      content_by_lua_block {
        local session, err = require "resty.session".start()
        session:set_subject("Fan de Lua")
        session:set("quote", "Lorem ipsum dolor sit amet")
        local _, err_save = session:save()

        ngx.say(string.format([[
          <html>
          <body>
            <p>La sesión fue modificada (%s)</p>
            <p><a href=/modified>Ver si está modificada</a></p>
          </body>
          </html>
        ]], err or err_save or "sin error"))
      }
    }

    location /modified {
      content_by_lua_block {
        local session, err = require "resty.session".start()

        ngx.say(string.format([[
          <html>
          <body>
            <p>La sesión fue iniciada por %s (%s)</p>
            <p><blockquote>%s</blockquote></p>
            <p><a href=/destroy>Destruir la sesión</a></p>
          </body>
          </html>
        ]],
          session:get_subject() or "Anónimo",
          err or "sin error",
          session:get("quote")  or "sin cita"
        ))
      }
    }

    location /destroy {
      content_by_lua_block {
        local ok, err = require "resty.session".destroy()

        ngx.say(string.format([[
          <html>
          <body>
            <p>La sesión fue destruida (%s)</p>
            <p><a href=/destroyed>¿Verificar que realmente fue?</a></p>
          </body>
          </html>
        ]], err or "sin error"))
      }
    }

    location /destroyed {
      content_by_lua_block {
        local session, err = require "resty.session".open()

        ngx.say(string.format([[
          <html>
          <body>
            <p>La sesión fue realmente destruida, eres conocido como %s (%s)</p>
            <p><a href=/>Iniciar de nuevo</a></p>
          </body>
          </html>
        ]],
          session:get_subject() or "Anónimo",
          err or "sin error"
        ))
      }
    }
  }
}

Configuración

La configuración se puede dividir en configuración de sesión genérica y configuración de almacenamiento del lado del servidor.

Aquí hay un ejemplo:

init_by_lua_block {
  require "resty.session".init({
    remember = true,
    store_metadata = true,
    secret = "RaJKp8UQW1",
    secret_fallbacks = {
      "X88FuG1AkY",
      "fxWNymIpbb",
    },
    storage = "postgres",
    postgres = {
      username = "my-service",
      password = "kVgIXCE5Hg",
      database = "sessions",
    },
  })
}

Configuración de Sesión

La configuración de la sesión se puede pasar a las funciones de inicialización, constructor y ayuda.

Aquí están las posibles opciones de configuración de sesión:

Opción Predeterminado Descripción
secret nil Secreto utilizado para la derivación de la clave. El secreto se hash con SHA-256 antes de usarlo. Ej. "RaJKp8UQW1".
secret_fallbacks nil Array de secretos que pueden usarse como secretos alternativos (al hacer rotación de claves), Ej. { "6RfrAYYzYq", "MkbTkkyF9C" }.
ikm (aleatorio) El material de clave inicial (o ikm) se puede especificar directamente (sin usar un secreto) con exactamente 32 bytes de datos. Ej. "5ixIW4QVMk0dPtoIhn41Eh1I9enP2060"
ikm_fallbacks nil Array de materiales de clave inicial que pueden usarse como claves alternativas (al hacer rotación de claves), Ej. { "QvPtlPKxOKdP5MCu1oI3lOEXIVuDckp7" }.
cookie_prefix nil Prefijo de la cookie, usa nil, "__Host-" o "__Secure-".
cookie_name "session" Nombre de la cookie de sesión, Ej. "session".
cookie_path "/" Ruta de la cookie, Ej. "/".
cookie_domain nil Dominio de la cookie, Ej. "example.com"
cookie_http_only true Marca la cookie como solo HTTP, usa true o false.
cookie_secure nil Marca la cookie como segura, usa nil, true o false.
cookie_priority nil Prioridad de la cookie, usa nil, "Low", "Medium" o "High".
cookie_same_site "Lax" Política de misma-sitio de la cookie, usa nil, "Lax", "Strict", "None" o "Default"
cookie_same_party nil Marca la cookie con la bandera de misma parte, usa nil, true o false.
cookie_partitioned nil Marca la cookie con la bandera de particionado, usa nil, true o false.
remember false Habilita o deshabilita sesiones persistentes, usa nil, true o false.
remember_safety "Medium" Complejidad de derivación de clave de cookie de recuerdo, usa nil, "None" (rápido), "Low", "Medium", "High" o "Very High" (lento).
remember_cookie_name "remember" Nombre de la cookie de sesión persistente, Ej. "remember".
audience "default" Audiencia de la sesión, Ej. "my-application".
subject nil Sujeto de la sesión, Ej. "[email protected]".
enforce_same_subject false Cuando se establece en true, las audiencias deben compartir el mismo sujeto. La biblioteca elimina los datos de audiencia que no coinciden con el sujeto al guardar.
stale_ttl 10 Cuando se guarda la sesión, se crea una nueva sesión, el ttl obsoleto especifica cuánto tiempo puede seguir usándose la antigua, Ej. 10 (en segundos).
idling_timeout 900 El tiempo de espera de inactividad especifica cuánto tiempo puede estar inactiva la sesión hasta que se considere inválida, Ej. 900 (15 minutos) (en segundos), 0 desactiva las verificaciones y el toque.
rolling_timeout 3600 El tiempo de espera rodante especifica cuánto tiempo puede usarse la sesión hasta que necesite ser renovada, Ej. 3600 (una hora) (en segundos), 0 desactiva las verificaciones y el rodado.
absolute_timeout 86400 El tiempo de espera absoluto limita cuánto tiempo puede renovarse la sesión, hasta que se requiera re-autenticación, Ej. 86400 (un día) (en segundos), 0 desactiva las verificaciones.
remember_rolling_timeout 604800 El tiempo de espera de recuerdo especifica cuánto tiempo se considera válida la sesión persistente, Ej. 604800 (una semana) (en segundos), 0 desactiva las verificaciones y el rodado.
remember_absolute_timeout 2592000 El tiempo de espera absoluto de recuerdo limita cuánto tiempo puede renovarse la sesión persistente, hasta que se requiera re-autenticación, Ej. 2592000 (30 días) (en segundos), 0 desactiva las verificaciones.
hash_storage_key false Si se debe o no hash la clave de almacenamiento. Con la clave de almacenamiento hash, es imposible descifrar datos en el lado del servidor sin tener una cookie también, usa nil, true o false.
hash_subject false Si se debe o no hash el sujeto cuando store_metadata está habilitado, Ej. por razones de PII.
store_metadata false Si también se deben almacenar los metadatos de las sesiones, como la recopilación de datos de sesiones para una audiencia específica perteneciente a un sujeto específico.
touch_threshold 60 El umbral de toque controla con qué frecuencia o infrecuencia el session:refresh toca la cookie, Ej. 60 (un minuto) (en segundos)
compression_threshold 1024 El umbral de compresión controla cuándo se desinflan los datos, Ej. 1024 (un kilobyte) (en bytes), 0 desactiva la compresión.
bind nil Vincula la sesión a datos adquiridos de la solicitud HTTP o conexión, usa ip, scheme, user-agent. Ej. { "scheme", "user-agent" } calculará MAC utilizando también Scheme de la solicitud HTTP y el encabezado User-Agent.
request_headers nil Conjunto de encabezados para enviar a upstream, usa id, audience, subject, timeout, idling-timeout, rolling-timeout, absolute-timeout. Ej. { "id", "timeout" } establecerá los encabezados de solicitud Session-Id y Session-Timeout cuando se llame a set_headers.
response_headers nil Conjunto de encabezados para enviar a downstream, usa id, audience, subject, timeout, idling-timeout, rolling-timeout, absolute-timeout. Ej. { "id", "timeout" } establecerá los encabezados de respuesta Session-Id y Session-Timeout cuando se llame a set_headers.
storage nil El almacenamiento es responsable de almacenar los datos de la sesión, usa nil o "cookie" (los datos se almacenan en la cookie), "dshm", "file", "memcached", "mysql", "postgres", "redis" o "shm", o da un nombre de módulo personalizado ("custom-storage"), o una table que implemente la interfaz de almacenamiento de sesión.
dshm nil Configuración para almacenamiento dshm, Ej. { prefix = "sessions" } (ver abajo)
file nil Configuración para almacenamiento en archivo, Ej. { path = "/tmp", suffix = "session" } (ver abajo)
memcached nil Configuración para almacenamiento en memcached, Ej. { prefix = "sessions" } (ver abajo)
mysql nil Configuración para almacenamiento en MySQL / MariaDB, Ej. { database = "sessions" } (ver abajo)
postgres nil Configuración para almacenamiento en Postgres, Ej. { database = "sessions" } (ver abajo)
redis nil Configuración para almacenamiento en Redis / Redis Sentinel / Redis Cluster, Ej. { prefix = "sessions" } (ver abajo)
shm nil Configuración para almacenamiento en memoria compartida, Ej. { zone = "sessions" }
["custom-storage"] nil configuración de almacenamiento personalizado (cargado con require "custom-storage").

Al almacenar datos en una cookie, no se requiere configuración adicional, solo establece el storage en nil o "cookie".

Configuración de Almacenamiento DSHM

Con el almacenamiento DSHM puedes usar la siguiente configuración (establece el storage en "dshm"):

Opción Predeterminado Descripción
prefix nil El prefijo para las claves almacenadas en DSHM.
suffix nil El sufijo para las claves almacenadas en DSHM.
host "127.0.0.1" El host al que conectarse.
port 4321 El puerto al que conectarse.
connect_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método connect del objeto de socket TCP/dominio-unix.
send_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método send del objeto de socket TCP/dominio-unix.
read_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método receive del objeto de socket TCP/dominio-unix.
keepalive_timeout nil Controla el tiempo máximo de inactividad de las conexiones en el grupo de conexiones.
pool nil Un nombre personalizado para el grupo de conexiones que se está utilizando.
pool_size nil El tamaño del grupo de conexiones.
backlog nil Un tamaño de cola para usar cuando el grupo de conexiones está lleno (configurado con pool_size).
ssl nil Habilitar SSL.
ssl_verify nil Verificar el certificado del servidor.
server_name nil El nombre del servidor para la nueva extensión TLS Server Name Indication (SNI).

Por favor, consulta ngx-distributed-shm para instalar las dependencias necesarias.

Configuración de Almacenamiento en Archivo

Con el almacenamiento en archivo puedes usar la siguiente configuración (establece el storage en "file"):

Opción Predeterminado Descripción
prefix nil Prefijo de archivo para el archivo de sesión.
suffix nil Sufijo de archivo (o extensión sin .) para el archivo de sesión.
pool nil Nombre del grupo de hilos bajo el cual ocurre la escritura en archivo (disponible solo en Linux).
path (directorio tmp) Ruta (o directorio) bajo el cual se crean los archivos de sesión.

La implementación requiere LuaFileSystem que puedes instalar con LuaRocks:

 luarocks install LuaFileSystem

Configuración de Almacenamiento en Memcached

Con el almacenamiento en Memcached puedes usar la siguiente configuración (establece el storage en "memcached"):

Opción Predeterminado Descripción
prefix nil Prefijo para las claves almacenadas en memcached.
suffix nil Sufijo para las claves almacenadas en memcached.
host 127.0.0.1 El host al que conectarse.
port 11211 El puerto al que conectarse.
socket nil El archivo de socket al que conectarse.
connect_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método connect del objeto de socket TCP/dominio-unix.
send_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método send del objeto de socket TCP/dominio-unix.
read_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método receive del objeto de socket TCP/dominio-unix.
keepalive_timeout nil Controla el tiempo máximo de inactividad de las conexiones en el grupo de conexiones.
pool nil Un nombre personalizado para el grupo de conexiones que se está utilizando.
pool_size nil El tamaño del grupo de conexiones.
backlog nil Un tamaño de cola para usar cuando el grupo de conexiones está lleno (configurado con pool_size).
ssl false Habilitar SSL
ssl_verify nil Verificar el certificado del servidor.
server_name nil El nombre del servidor para la nueva extensión TLS Server Name Indication (SNI).

Configuración de Almacenamiento en MySQL / MariaDB

Con el almacenamiento en MySQL / MariaDB puedes usar la siguiente configuración (establece el storage en "mysql"):

Opción Predeterminado Descripción
host "127.0.0.1" El host al que conectarse.
port 3306 El puerto al que conectarse.
socket nil El archivo de socket al que conectarse.
username nil El nombre de usuario de la base de datos para autenticarse.
password nil Contraseña para la autenticación, puede ser requerida dependiendo de la configuración del servidor.
charset "ascii" El conjunto de caracteres utilizado en la conexión MySQL.
database nil El nombre de la base de datos a la que conectarse.
table_name "sessions" Nombre de la tabla de base de datos a la que almacenar los datos de sesión.
table_name_meta "sessions_meta" Nombre de la tabla de metadatos de la base de datos a la que almacenar los metadatos de sesión.
max_packet_size 1048576 El límite superior para los paquetes de respuesta enviados desde el servidor MySQL (en bytes).
connect_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método connect del objeto de socket TCP/dominio-unix.
send_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método send del objeto de socket TCP/dominio-unix.
read_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método receive del objeto de socket TCP/dominio-unix.
keepalive_timeout nil Controla el tiempo máximo de inactividad de las conexiones en el grupo de conexiones.
pool nil Un nombre personalizado para el grupo de conexiones que se está utilizando.
pool_size nil El tamaño del grupo de conexiones.
backlog nil Un tamaño de cola para usar cuando el grupo de conexiones está lleno (configurado con pool_size).
ssl false Habilitar SSL.
ssl_verify nil Verificar el certificado del servidor.

También necesitas crear las siguientes tablas en tu base de datos:

--
-- Tabla de base de datos que almacena datos de sesión.
--
CREATE TABLE IF NOT EXISTS sessions (
  sid  CHAR(43) PRIMARY KEY,
  name VARCHAR(255),
  data MEDIUMTEXT,
  exp  DATETIME,
  INDEX (exp)
) CHARACTER SET ascii;

--
-- Tabla de metadatos de sesiones.
--
-- Esto solo es necesario si deseas almacenar metadatos de sesión.
--
CREATE TABLE IF NOT EXISTS sessions_meta (
  aud VARCHAR(255),
  sub VARCHAR(255),
  sid CHAR(43),
  PRIMARY KEY (aud, sub, sid),
  CONSTRAINT FOREIGN KEY (sid) REFERENCES sessions(sid) ON DELETE CASCADE ON UPDATE CASCADE
) CHARACTER SET ascii;

Configuración de Postgres

Con el almacenamiento en Postgres puedes usar la siguiente configuración (establece el storage en "postgres"):

Opción Predeterminado Descripción
host "127.0.0.1" El host al que conectarse.
port 5432 El puerto al que conectarse.
application 5432 Establece el nombre de la conexión como se muestra en pg_stat_activity (por defecto es "pgmoon").
username "postgres" El nombre de usuario de la base de datos para autenticarse.
password nil Contraseña para la autenticación, puede ser requerida dependiendo de la configuración del servidor.
database nil El nombre de la base de datos a la que conectarse.
table_name "sessions" Nombre de la tabla de base de datos a la que almacenar los datos de sesión (puede estar precedido por el esquema de base de datos).
table_name_meta "sessions_meta" Nombre de la tabla de metadatos de la base de datos a la que almacenar los metadatos de sesión (puede estar precedido por el esquema de base de datos).
connect_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método connect del objeto de socket TCP/dominio-unix.
send_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método send del objeto de socket TCP/dominio-unix.
read_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método receive del objeto de socket TCP/dominio-unix.
keepalive_timeout nil Controla el tiempo máximo de inactividad de las conexiones en el grupo de conexiones.
pool nil Un nombre personalizado para el grupo de conexiones que se está utilizando.
pool_size nil El tamaño del grupo de conexiones.
backlog nil Un tamaño de cola para usar cuando el grupo de conexiones está lleno (configurado con pool_size).
ssl false Habilitar SSL.
ssl_verify nil Verificar el certificado del servidor.
ssl_required nil Abortará la conexión si el servidor no admite conexiones SSL.

También necesitas crear las siguientes tablas en tu base de datos:

--
-- Tabla de base de datos que almacena datos de sesión.
--
CREATE TABLE IF NOT EXISTS sessions (
  sid  TEXT PRIMARY KEY,
  name TEXT,
  data TEXT,
  exp  TIMESTAMP WITH TIME ZONE
);
CREATE INDEX ON sessions (exp);

--
-- Tabla de metadatos de sesiones.
--
-- Esto solo es necesario si deseas almacenar metadatos de sesión.
--
CREATE TABLE IF NOT EXISTS sessions_meta (
  aud TEXT,
  sub TEXT,
  sid TEXT REFERENCES sessions (sid) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (aud, sub, sid)
);

La implementación requiere pgmoon que puedes instalar con LuaRocks:

 luarocks install pgmoon

Configuración de Redis

La biblioteca de sesiones admite conexiones de Redis único, Redis Sentinel y Redis Cluster. Configuraciones comunes entre todos ellos:

Opción Predeterminado Descripción
prefix nil Prefijo para las claves almacenadas en Redis.
suffix nil Sufijo para las claves almacenadas en Redis.
username nil El nombre de usuario de la base de datos para autenticarse.
password nil Contraseña para la autenticación.
connect_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método connect del objeto de socket TCP/dominio-unix.
send_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método send del objeto de socket TCP/dominio-unix.
read_timeout nil Controla el valor de tiempo de espera predeterminado utilizado en el método receive del objeto de socket TCP/dominio-unix.
keepalive_timeout nil Controla el tiempo máximo de inactividad de las conexiones en el grupo de conexiones.
pool nil Un nombre personalizado para el grupo de conexiones que se está utilizando.
pool_size nil El tamaño del grupo de conexiones.
backlog nil Un tamaño de cola para usar cuando el grupo de conexiones está lleno (configurado con pool_size).
ssl false Habilitar SSL.
ssl_verify nil Verificar el certificado del servidor.
server_name nil El nombre del servidor para la nueva extensión TLS Server Name Indication (SNI).

La implementación de redis único se selecciona cuando no pasas ni sentinels ni nodes, lo que llevaría a seleccionar la implementación de sentinel o cluster.

Configuración de Redis Único

Redis único tiene las siguientes opciones de configuración adicionales (establece el storage en "redis"):

Opción Predeterminado Descripción
host "127.0.0.1" El host al que conectarse.
port 6379 El puerto al que conectarse.
socket nil El archivo de socket al que conectarse.

Configuración de Redis Sentinels

Redis Sentinel tiene las siguientes opciones de configuración adicionales (establece el storage en "redis" y configura los sentinels):

Opción Predeterminado Descripción
master nil Nombre del maestro.
role nil "master" o "slave".
socket nil El archivo de socket al que conectarse.
sentinels nil Redis Sentinels.
sentinel_username nil Nombre de usuario opcional de sentinel.
sentinel_password nil Contraseña opcional de sentinel.
database nil La base de datos a la que conectarse.

Los sentinels son un array de registros de Sentinel:

Opción Predeterminado Descripción
host nil El host al que conectarse.
port nil El puerto al que conectarse.

La implementación de sentinel se selecciona cuando pasas sentinels como parte de la configuración de redis (y no pasas nodes, lo que seleccionaría la implementación de cluster).

La implementación requiere lua-resty-redis-connector que puedes instalar con LuaRocks:

 luarocks install lua-resty-redis-connector

Configuración de Redis Cluster

Redis Cluster tiene las siguientes opciones de configuración adicionales (establece el storage en "redis" y configura los nodes):

Opción Predeterminado Descripción
name nil Nombre del clúster de Redis.
nodes nil Nodos del clúster de Redis.
lock_zone nil Nombre del diccionario compartido para bloqueos.
lock_prefix nil Prefijo del nombre del diccionario compartido para bloqueos.
max_redirections nil Máximo número de intentos de re-dirección.
max_connection_attempts nil Máximo número de intentos de conexión.
max_connection_timeout nil Tiempo máximo de espera de conexión en total entre los intentos.

Los nodes son un array de registros de nodos del clúster:

Opción Predeterminado Descripción
ip "127.0.0.1" La dirección IP a la que conectarse.
port 6379 El puerto al que conectarse.

La implementación de cluster se selecciona cuando pasas nodes como parte de la configuración de redis.

Para que el cluster funcione correctamente, necesitas configurar lock_zone, así que también agrega esto a tu configuración de Nginx:

lua_shared_dict redis_cluster_locks 100k;

Y establece el lock_zone en "redis_cluster_locks".

La implementación requiere resty-redis-cluster o kong-redis-cluster que puedes instalar con LuaRocks:

 luarocks install resty-redis-cluster
## o luarocks install kong-redis-cluster

Configuración SHM

Con el almacenamiento SHM puedes usar la siguiente configuración (establece el storage en "shm"):

Opción Predeterminado Descripción
prefix nil Prefijo para las claves almacenadas en SHM.
suffix nil Sufijo para las claves almacenadas en SHM.
zone "sessions" Un nombre de zona de memoria compartida.

También necesitarás crear un diccionario compartido zone en Nginx:

lua_shared_dict sessions 10m;

Nota: es posible que necesites ajustar el tamaño de la zona de memoria compartida según tus necesidades.

API

Los documentos de API generados por LDoc también se pueden ver en bungle.github.io/lua-resty-session.

Inicialización

session.init

sintaxis: session.init(configuration)

Inicializa la biblioteca de sesiones.

Esta función se puede llamar en las fases init o init_worker en OpenResty para establecer la configuración predeterminada global para todas las instancias de sesión creadas por esta biblioteca.

require "resty.session".init({
  audience = "my-application",
  storage = "redis",
  redis = {
    username = "session",
    password = "storage",
  },
})

Consulta configuration para posibles configuraciones.

Constructores

session.new

sintaxis: session = session.new(configuration)

Crea una nueva instancia de sesión.

local session = require "resty.session".new()
-- O
local session = require "resty.session".new({
  audience = "my-application",
})

Consulta configuration para posibles configuraciones.

Ayudantes

session.open

sintaxis: session, err, exists = session.open(configuration)

Esto se puede usar para abrir una sesión, y devolverá una sesión existente o una nueva. El parámetro de retorno exists (un booleano) indica si se devolvió una sesión existente o nueva. El err (una cadena) contiene un mensaje de por qué la apertura podría haber fallado (la función aún devolverá session también).

local session = require "resty.session".open()
-- O
local session, err, exists = require "resty.session".open({
  audience = "my-application",
})

Consulta configuration para posibles configuraciones.

session.start

sintaxis: session, err, exists, refreshed = session.start(configuration)

Esto se puede usar para iniciar una sesión, y devolverá una sesión existente o una nueva. En caso de que haya una sesión existente, la sesión también se actualizará (según sea necesario). El parámetro de retorno exists (un booleano) indica si se devolvió una sesión existente o nueva. El refreshed (un booleano) indica si la llamada a refresh fue exitosa. El err (una cadena) contiene un mensaje de por qué la apertura o la actualización podrían haber fallado (la función aún devolverá session también).

local session = require "resty.session".start()
-- O
local session, err, exists, refreshed = require "resty.session".start({
  audience = "my-application",
})

Consulta configuration para posibles configuraciones.

session.logout

sintaxis: ok, err, exists, logged_out = session.logout(configuration)

Cierra sesión de una audiencia específica.

Una sola cookie de sesión puede compartirse entre múltiples audiencias (o aplicaciones), por lo que existe la necesidad de poder cerrar sesión de solo una audiencia mientras se mantiene la sesión para las otras audiencias. El parámetro de retorno exists (un booleano) indica si la sesión existía. El parámetro de retorno logged_out (un booleano) señala si la sesión existía y también se cerró. El err (una cadena) contiene una razón por la cual la sesión no existía o por qué falló el cierre de sesión. El ok (veraz) será true cuando la sesión existía y se cerró con éxito.

Cuando hay solo una audiencia, esto se puede considerar igual a session.destroy.

Cuando la última audiencia cierra sesión, la cookie también se destruirá e invalidará en el cliente.

require "resty.session".logout()
-- O
local ok, err, exists, logged_out = require "resty.session".logout({
  audience = "my-application",
})

Consulta configuration para posibles configuraciones.

session.destroy

sintaxis: ok, err, exists, destroyed = session.destroy(configuration)

Destruye toda la sesión y borra las cookies.

Una sola cookie de sesión puede compartirse entre múltiples audiencias (o aplicaciones), por lo que existe la necesidad de poder cerrar sesión de solo una audiencia mientras se mantiene la sesión para las otras audiencias. El parámetro de retorno exists (un booleano) indica si la sesión existía. El parámetro de retorno destroyed (un booleano) señala si la sesión existía y también fue destruida. El err (una cadena) contiene una razón por la cual la sesión no existía o por qué falló el cierre de sesión. El ok (veraz) será true cuando la sesión existía y fue cerrada con éxito.

require "resty.session".destroy()
-- O
local ok, err, exists, destroyed = require "resty.session".destroy({
  cookie_name = "auth",
})

Consulta configuration para posibles configuraciones.

Métodos de Instancia

session:open

sintaxis: ok, err = session:open()

Esto se puede usar para abrir una sesión. Devuelve true cuando la sesión fue abierta y validada. De lo contrario, devuelve nil y un mensaje de error.

local session = require "resty.session".new()
local ok, err = session:open()
if ok then
  -- la sesión existe

else
  -- la sesión no existía o era inválida
end

session:save

sintaxis: ok, err = session:save()

Guarda los datos de la sesión y emite una nueva cookie de sesión con un nuevo id de sesión. Cuando remember está habilitado, también emitirá una nueva cookie persistente y posiblemente guardará los datos en el almacén de backend. Devuelve true cuando la sesión fue guardada. De lo contrario, devuelve nil y un mensaje de error.

local session = require "resty.session".new()
session:set_subject("john")
local ok, err = session:save()
if not ok then
  -- error al guardar la sesión
end

session:touch

sintaxis: ok, err = session:touch()

Actualiza el desplazamiento de inactividad de la sesión enviando una cookie de sesión actualizada. Solo envía la cookie del cliente y nunca llama a ninguna API de almacenamiento de sesión de backend. Normalmente, el session:refresh se usa para llamar a esto indirectamente. En caso de error, devuelve nil y un mensaje de error, de lo contrario true.

local session, err, exists = require "resty.session".open()
if exists then
  ok, err = session:touch()
end

session:refresh

sintaxis: ok, err = session:refresh()

Ya sea guarda la sesión (creando un nuevo id de sesión) o toca la sesión dependiendo de si el tiempo de espera rodante se está acercando, lo que significa que por defecto cuando se ha gastado 3/4 del tiempo de espera rodante, es decir, 45 minutos con el tiempo de espera rodante predeterminado de una hora. El toque tiene un umbral, por defecto de un minuto, por lo que puede omitirse en algunos casos (puedes llamar a session:touch() para forzarlo). En caso de error, devuelve nil y un mensaje de error, de lo contrario true.

local session, err, exists = require "resty.session".open()
if exists then
  local ok, err = session:refresh()
end

El código anterior se parece un poco al ayudante session.start().

session:logout

sintaxis: ok, err = session:logout()

Cerrar sesión destruye la sesión o simplemente borra los datos para la audiencia actual, y los guarda (cerrando sesión de la audiencia actual). En caso de error, devuelve nil y un mensaje de error, de lo contrario true.

local session, err, exists = require "resty.session".open()
if exists then
  local ok, err = session:logout()
end

session:destroy

sintaxis: ok, err = session:destroy()

Destruye la sesión y borra las cookies. En caso de error, devuelve nil y un mensaje de error, de lo contrario true.

local session, err, exists = require "resty.session".open()
if exists then
  local ok, err = session:destroy()
end

session:close

sintaxis: session:close()

Simplemente cierra la instancia de sesión para que no pueda ser utilizada más.

local session = require "resty.session".new()
session:set_subject("john")
local ok, err = session:save()
if not ok then
  -- error al guardar la sesión
end
session:close()

session:set_data

sintaxis: session:set_data(data)

Establece los datos de la sesión. Los data deben ser una table.

local session, err, exists = require "resty.session".open()
if not exists then
   session:set_data({
     cart = {},
   })
  session:save()
end

session:get_data

sintaxis: data = session:get_data()

Obtiene los datos de la sesión.

local session, err, exists = require "resty.session".open()
if exists then
  local data = session:get_data()
  ngx.req.set_header("Authorization", "Bearer " .. data.access_token)
end

session:set

sintaxis: session:set(key, value)

Establece un valor en la sesión.

local session, err, exists = require "resty.session".open()
if not exists then
  session:set("access-token", "eyJ...")
  session:save()
end

session:get

sintaxis: value = session:get(key)

Obtiene un valor de la sesión.

local session, err, exists = require "resty.session".open()
if exists then
  local access_token = session:get("access-token")
  ngx.req.set_header("Authorization", "Bearer " .. access_token)
end

session:set_audience

sintaxis: session:set_audience(audience)

Establece la audiencia de la sesión.

local session = require "resty.session".new()
session.set_audience("my-service")

session:get_audience

sintaxis: audience = session:get_audience()

Establece el sujeto de la sesión.

session:set_subject

sintaxis: session:set_subject(subject)

Establece la audiencia de la sesión.

local session = require "resty.session".new()
session.set_subject("[email protected]")

session:get_subject

sintaxis: subject = session:get_subject()

Obtiene el sujeto de la sesión.

local session, err, exists = require "resty.session".open()
if exists then
  local subject = session.get_subject()
end

session:get_property

sintaxis: value = session:get_property(name)

Obtiene la propiedad de la sesión. Posibles nombres de propiedades:

  • "id": id de sesión de 43 bytes (igual que nonce, pero codificado en base64 url)
  • "nonce": nonce de 32 bytes (igual que el id de sesión pero en bytes crudos)
  • "audience": Audiencia actual de la sesión
  • "subject": Sujeto actual de la sesión
  • "timeout": Tiempo de espera más cercano (en segundos) (lo que queda de él)
  • "idling-timeout": Tiempo de espera de inactividad de la sesión (en segundos) (lo que queda de él)
  • "rolling-timeout": Tiempo de espera rodante de la sesión (en segundos) (lo que queda de él)
  • "absolute-timeout": Tiempo de espera absoluto de la sesión (en segundos) (lo que queda de él)

Nota: el valor devuelto puede ser nil.

local session, err, exists = require "resty.session".open()
if exists then
  local timeout = session.get_property("timeout")
end

session:set_remember

sintaxis: session:set_remember(value)

Establece sesiones persistentes activadas/desactivadas.

En muchos formularios de inicio de sesión, se le da al usuario una opción de "recuérdame". Puedes llamar a esta función según lo que seleccionó el usuario.

local session = require "resty.session".new()
if ngx.var.args.remember then
  session:set_remember(true)
end
session:set_subject(ngx.var.args.username)
session:save()

session:get_remember

sintaxis: remember = session:get_remember()

Obtiene el estado de las sesiones persistentes.

local session, err, exists = require "resty.session".open()
if exists then
  local remember = session.get_remember()
end

sintaxis: ok, err = session:clear_request_cookie()

Modifica los encabezados de la solicitud eliminando las cookies relacionadas con la sesión. Esto es útil cuando usas la biblioteca de sesiones en un servidor proxy y no deseas que las cookies de sesión se reenvíen al servicio upstream. En caso de error, devuelve nil y un mensaje de error, de lo contrario true (que se puede ignorar).

local session, err, exists = require "resty.session".open()
if exists then
  session:clear_request_cookie()
end

session:set_headers

sintaxis: ok, err = session:set_headers(arg1, arg2, ...)

Establece encabezados de solicitud y respuesta según la configuración. En caso de error, devuelve nil y un mensaje de error, de lo contrario true (que se puede ignorar).

local session, err, exists = require "resty.session".open({
  request_headers = { "audience", "subject", "id" },
  response_headers = { "timeout", "idling-timeout", "rolling-timeout", "absolute-timeout" },
})
if exists then
  session:set_headers()
end

Cuando se llama sin argumentos, establecerá los encabezados de solicitud configurados con request_headers y los encabezados de respuesta configurados con response_headers.

Consulta configuration para posibles nombres de encabezados.

session:set_request_headers

sintaxis: ok, err = session:set_request_headers(arg1, arg2, ...)

Establece encabezados de solicitud. En caso de error, devuelve nil y un mensaje de error, de lo contrario true (que se puede ignorar).

local session, err, exists = require "resty.session".open()
if exists then
  session:set_request_headers("audience", "subject", "id")
end

Cuando se llama sin argumentos, establecerá los encabezados de solicitud configurados con request_headers.

Consulta configuration para posibles nombres de encabezados.

session:set_response_headers

sintaxis: ok, err = session:set_response_headers(arg1, arg2, ...)

Establece encabezados de respuesta. En caso de error, devuelve nil y un mensaje de error, de lo contrario true (que se puede ignorar).

local session, err, exists = require "resty.session".open()
if exists then
  session:set_response_headers("timeout", "idling-timeout", "rolling-timeout", "absolute-timeout")
end

Cuando se llama sin argumentos, establecerá los encabezados de respuesta configurados con response_headers.

Consulta configuration para posibles nombres de encabezados.

session.info:set

sintaxis: session.info:set(key, value)

Establece un valor en el almacén de información de la sesión. El almacén de información de la sesión puede usarse en escenarios en los que deseas almacenar datos en el almacenamiento del lado del servidor, pero no deseas crear una nueva sesión y enviar una nueva cookie de sesión. Los datos del almacén de información no se consideran al verificar la etiqueta de autenticación o el código de autenticación de mensajes. Por lo tanto, si deseas usar esto para datos que necesitan ser cifrados, necesitas cifrar el valor antes de pasarlo a esta función.

local session, err, exists = require "resty.session".open()
if exists then
  session.info:set("last-access", ngx.now())
  session.info:save()
end

Con el almacenamiento en cookie, esto aún funciona, pero es casi lo mismo que session:set.

session.info:get

sintaxis: value = session.info:get(key)

Obtiene un valor del almacén de información de la sesión.

local session, err, exists = require "resty.session".open()
if exists then
  local last_access = session.info:get("last-access")
end

session.info:save

sintaxis: ok, err = session.info:save()

Guarda la información. Solo actualiza el almacenamiento de backend. No envía una nueva cookie (excepto con el almacenamiento en cookie).

local session = require "resty.session".new()
session.info:set("last-access", ngx.now())
local ok, err = session.info:save()
[ HEADER -------------------------------------------------------------------------------------]
[ Tipo || Flags || SID || Creado en || Desplazamiento Rodante || Tamaño || Etiqueta || Desplazamiento de Inactividad || Mac ]
[ 1B   || 2B    || 32B || 5B         || 4B                 || 3B   || 16B || 3B               || 16B ]

y

[ PAYLOAD --]
[ Datos  *B  ]

Tanto el HEADER como el PAYLOAD están codificados en base64 url antes de colocarse en un encabezado Set-Cookie. Al usar un almacenamiento del lado del servidor, el PAYLOAD no se coloca en la cookie. Con el almacenamiento en cookie, el encabezado codificado en base64 url se concatena con el PAYLOAD codificado en base64 url.

El HEADER tiene un tamaño fijo de 82 bytes en binario o 110 bytes en forma codificada en base64 url.

Campos del encabezado explicados:

  • Tipo: número 1 empaquetado en un solo byte little endian (actualmente el único tipo soportado).
  • Flags: flags empaquetados en binario (corto) en una forma de dos bytes little endian.
  • SID: 32 bytes de datos aleatorios criptográficos (ID de sesión).
  • Creado en: segundos empaquetados en binario desde la época en una forma little endian, truncados a 5 bytes.
  • Desplazamiento Rodante: segundos empaquetados en binario desde el momento de creación en una forma little endian (entero).
  • Tamaño: tamaño de datos empaquetado en binario en una forma de tres bytes little endian.
  • Etiqueta: 16 bytes de etiqueta de autenticación del cifrado AES-256-GCM de los datos.
  • Desplazamiento de Inactividad: segundos empaquetados en binario desde el momento de creación + desplazamiento rodante en una forma little endian, truncados a 3 bytes.
  • Mac: 16 bytes de código de autenticación de mensajes del encabezado.

Cifrado de Datos

  1. Material de clave inicial (IKM):
  2. derivar IKM del secret hashando secret con SHA-256, o
  3. usar IKM de 32 bytes cuando se pase a la biblioteca con ikm
  4. Generar 32 bytes de ID de sesión aleatorio criptográfico (sid)
  5. Derivar una clave de cifrado de 32 bytes y un vector de inicialización de 12 bytes con HKDF usando SHA-256 (en modo FIPS utiliza PBKDF2 con SHA-256 en su lugar)
  6. Usar HKDF extract para derivar una nueva clave de ikm para obtener key (este paso se puede hacer una vez por cada ikm):
    • longitud de salida: 32
    • digest: "sha256"
    • clave: <ikm>
    • modo: extract only
    • info: ""
    • sal: ""
  7. Usar HKDF expand para derivar 44 bytes de output:
    • longitud de salida: 44
    • digest: "sha256"
    • clave: <key>
    • modo: expand only
    • info: "encryption:<sid>"
    • sal: ""
  8. Los primeros 32 bytes de output son la clave de cifrado (aes-key), y los últimos 12 bytes son el vector de inicialización (iv)
  9. Cifrar plaintext (codificado en JSON y opcionalmente desinflado) usando AES-256-GCM para obtener ciphertext y tag
  10. cifrado: "aes-256-gcm"
  11. clave: <aes-key>
  12. iv: <iv>
  13. texto plano: <plaintext>
  14. aad: usar los primeros 47 bytes de header como aad, que incluye:
    1. Tipo
    2. Flags
    3. ID de sesión
    4. Tiempo de creación
    5. Desplazamiento rodante
    6. Tamaño de los datos

Hay una variación para las cookies de remember en el paso 3, donde podemos usar PBKDF2 en lugar de HKDF, dependiendo de la configuración de remember_safety (también lo usamos en modo FIPS). La configuración de PBKDF2:

  • longitud de salida: 44
  • digest: "sha256"
  • contraseña: <key>
  • sal: "encryption:<sid>"
  • iteraciones: <1000|10000|100000|1000000>
  • pkcs5: 1 (cumple con FIPS en nuestro caso de uso, pero se necesita desactivar las verificaciones basadas en SP800-132, como el recuento de iteraciones, ver: https://docs.openssl.org/master/man7/provider-kdf/#kdf-parameters)

Los recuentos de iteración se basan en la configuración de remember_safety ("Low", "Medium", "High", "Very High"), si remember_safety se establece en "None", utilizaremos el HDKF como se mencionó anteriormente.

Nota: Para compatibilidad hacia atrás, desactivamos las verificaciones de cumplimiento de SP800-132 en modo FIPS. Estas verificaciones comprueban que la longitud de la sal sea de al menos 128 bits, la longitud de la clave derivada sea de al menos 112 bits y que el recuento de iteraciones sea de al menos 1000. Estas verificaciones están desactivadas por defecto en el proveedor predeterminado de OpenSSL, pero están habilitadas por defecto en el proveedor FIPS.

  1. Deriva una clave de autenticación de 32 bytes (mac_key) con HKDF usando SHA-256 (en modo FIPS utiliza PBKDF2 con SHA-256 en su lugar):
    1. Usar HKDF extract para derivar una nueva clave de ikm para obtener key (este paso se puede hacer una vez por cada ikm y reutilizarse con la generación de clave de cifrado):
      • longitud de salida: 32
      • digest: "sha256"
      • clave: <ikm>
      • modo: extract only
      • info: ""
      • sal: ""
    2. Usar HKDF expand para derivar 32 bytes de mac-key:
      • longitud de salida: 32
      • digest: "sha256"
      • clave: <key>
      • modo: expand only
      • info: "authentication:<sid>"
      • sal: ""
  2. Calcular el código de autenticación de mensajes usando HMAC-SHA256:
  3. digest: "sha256"
  4. clave: <mac-key>
  5. mensaje: usar los primeros 66 bytes de header, que incluye:
    1. Tipo
    2. Flags
    3. ID de sesión
    4. Tiempo de creación
    5. Desplazamiento rodante
    6. Tamaño de datos
    7. Etiqueta
    8. Desplazamiento de inactividad

Interfaz de Almacenamiento Personalizado

Si deseas implementar un almacenamiento personalizado, necesitas implementar la siguiente interfaz:

---
-- <custom> backend para la biblioteca de sesiones
--
-- @module <custom>


---
-- Almacenamiento
-- @section instance


local metatable = {}


metatable.__index = metatable


function metatable.__newindex()
  error("intento de actualizar una tabla de solo lectura", 2)
end


---
-- Almacena datos de sesión.
--
-- @function instance:set
-- @tparam string name nombre de la cookie
-- @tparam string key clave de sesión
-- @tparam string value valor de sesión
-- @tparam number ttl ttl de sesión
-- @tparam number current_time tiempo actual
-- @tparam[opt] string old_key id de sesión antiguo
-- @tparam string stale_ttl ttl obsoleto
-- @tparam[opt] table metadata tabla de metadatos
-- @treturn true|nil ok
-- @treturn string mensaje de error
function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata)
  -- NYI
end


---
-- Recupera datos de sesión.
--
-- @function instance:get
-- @tparam string name nombre de la cookie
-- @tparam string key clave de sesión
-- @treturn string|nil datos de sesión
-- @treturn string mensaje de error
function metatable:get(name, key)
  -- NYI
end


---
-- Elimina datos de sesión.
--
-- @function instance:delete
-- @tparam string name nombre de la cookie
-- @tparam string key clave de sesión
-- @tparam[opt] table metadata metadatos de sesión
-- @treturn boolean|nil datos de sesión
-- @treturn string mensaje de error
function metatable:delete(name, key, current_time, metadata)
  -- NYI
end


local storage = {}


---
-- Constructores
-- @section constructors


---
-- Configuración
-- @section configuration


---
-- Configuración de almacenamiento <custom>
-- @field <field-name> TBD
-- @table configuration


---
-- Crea un almacenamiento <custom>.
--
-- Esto crea una nueva instancia de almacenamiento en memoria compartida.
--
-- @function module.new
-- @tparam[opt]  table   configuration  almacenamiento <custom> @{configuration}
-- @treturn      table                  instancia de almacenamiento <custom>
function storage.new(configuration)
  -- NYI
  -- return setmetatable({}, metatable)
end


return storage

Por favor, consulta las implementaciones existentes para los detalles. Y por favor, haz una solicitud de extracción para que podamos integrarlo directamente a la biblioteca para otros usuarios también.

GitHub

Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-session.