Перейти к содержанию

session: Библиотека сессий для nginx-module-lua – гибкая и безопасная

Установка

Если вы еще не настроили подписку на RPM-репозиторий, зарегистрируйтесь. Затем вы можете продолжить с следующими шагами.

CentOS/RHEL 7 или 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

Чтобы использовать эту библиотеку Lua с NGINX, убедитесь, что nginx-module-lua установлен.

Этот документ описывает lua-resty-session v4.1.5, выпущенную 24 ноября 2025 года.


lua-resty-session — это безопасная и гибкая библиотека сессий для OpenResty.

TL;DR;

  • Сессии неизменяемы (каждое сохранение генерирует новую сессию) и без блокировок.
  • Данные сессии шифруются с помощью AES-256-GCM с ключом, полученным с использованием HKDF-SHA256 (в режиме FIPS используется PBKDF2 с SHA-256).
  • Сессия имеет фиксированный заголовок, который защищен HMAC-SHA256 MAC с ключом, полученным с использованием HKDF-SHA256 (в режиме FIPS используется PBKDF2 с SHA-256).
  • Данные сессии могут храниться в безсостоянии cookie или в различных бэкенд-хранилищах.
  • Один cookie сессии может поддерживать несколько сессий для разных аудиторий.

Примечание: Версия 4.0.0 была переписана с учетом множества уроков, усвоенных за годы. Если вы все еще используете более старую версию, пожалуйста, обратитесь к старой документации.

Синопсис

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>Начать тест</a>
          </body>
          </html>
        ]])
      }
    }

    location /start {
      content_by_lua_block {
        local session = require "resty.session".new()
        session:set_subject("Фанат OpenResty")
        session:set("quote", "Быстрая коричневая лиса перепрыгивает через ленивую собаку")
        local ok, err = session:save()

        ngx.say(string.format([[
          <html>
          <body>
            <p>Сессия начата (%s)</p>
            <p><a href=/started>Проверить, действительно ли это было</a></p>
          </body>
          </html>
        ]], err or "нет ошибок"))
      }
    }

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

        ngx.say(string.format([[
          <html>
          <body>
            <p>Сессия была начата %s (%s)</p>
            <p><blockquote>%s</blockquote></p>
            <p><a href=/modify>Изменить сессию</a></p>
          </body>
          </html>
        ]],
          session:get_subject() or "Аноним",
          err or "нет ошибок",
          session:get("quote") or "нет цитаты"
        ))
      }
    }

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

        ngx.say(string.format([[
          <html>
          <body>
            <p>Сессия была изменена (%s)</p>
            <p><a href=/modified>Проверить, изменена ли она</a></p>
          </body>
          </html>
        ]], err or err_save or "нет ошибок"))
      }
    }

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

        ngx.say(string.format([[
          <html>
          <body>
            <p>Сессия была начата %s (%s)</p>
            <p><blockquote>%s</blockquote></p>
            <p><a href=/destroy>Уничтожить сессию</a></p>
          </body>
          </html>
        ]],
          session:get_subject() or "Аноним",
          err or "нет ошибок",
          session:get("quote")  or "нет цитаты"
        ))
      }
    }

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

        ngx.say(string.format([[
          <html>
          <body>
            <p>Сессия была уничтожена (%s)</p>
            <p><a href=/destroyed>Проверить, действительно ли это было?</a></p>
          </body>
          </html>
        ]], err or "нет ошибок"))
      }
    }

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

        ngx.say(string.format([[
          <html>
          <body>
            <p>Сессия действительно была уничтожена, вы известны как %s (%s)</p>
            <p><a href=/>Начать снова</a></p>
          </body>
          </html>
        ]],
          session:get_subject() or "Аноним",
          err or "нет ошибок"
        ))
      }
    }
  }
}

Конфигурация

Конфигурацию можно разделить на общую конфигурацию сессий и конфигурацию хранилища на стороне сервера.

Вот пример:

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",
    },
  })
}

Конфигурация сессии

Конфигурация сессии может быть передана в функции инициализации, конструктора и помощника.

Вот возможные параметры конфигурации сессии:

Параметр По умолчанию Описание
secret nil Секрет, используемый для получения ключа. Секрет хешируется с помощью SHA-256 перед использованием. Например, "RaJKp8UQW1".
secret_fallbacks nil Массив секретов, которые могут использоваться в качестве альтернативных секретов (при ротации ключей), например, { "6RfrAYYzYq", "MkbTkkyF9C" }.
ikm (случайный) Начальный ключевой материал (или ikm) может быть указан напрямую (без использования секрета) с точно 32 байтами данных. Например, "5ixIW4QVMk0dPtoIhn41Eh1I9enP2060"
ikm_fallbacks nil Массив начальных ключевых материалов, которые могут использоваться в качестве альтернативных ключей (при ротации ключей), например, { "QvPtlPKxOKdP5MCu1oI3lOEXIVuDckp7" }.
cookie_prefix nil Префикс cookie, используйте nil, "__Host-" или "__Secure-".
cookie_name "session" Имя cookie сессии, например, "session".
cookie_path "/" Путь cookie, например, "/".
cookie_domain nil Домен cookie, например, "example.com"
cookie_http_only true Установить cookie только для HTTP, используйте true или false.
cookie_secure nil Установить cookie как безопасное, используйте nil, true или false.
cookie_priority nil Приоритет cookie, используйте nil, "Low", "Medium" или "High".
cookie_same_site "Lax" Политика same-site для cookie, используйте nil, "Lax", "Strict", "None" или "Default"
cookie_same_party nil Установить флаг cookie с тем же участником, используйте nil, true или false.
cookie_partitioned nil Установить флаг cookie с разделением, используйте nil, true или false.
remember false Включить или отключить постоянные сессии, используйте nil, true или false.
remember_safety "Medium" Сложность получения ключа cookie для запоминания, используйте nil, "None" (быстро), "Low", "Medium", "High" или "Very High" (медленно).
remember_cookie_name "remember" Имя cookie для постоянной сессии, например, "remember".
audience "default" Аудитория сессии, например, "my-application".
subject nil Тема сессии, например, "[email protected]".
enforce_same_subject false Если установлено в true, аудитории должны делить одну и ту же тему. Библиотека удаляет данные аудитории, которые не соответствуют теме, при сохранении.
stale_ttl 10 Когда сессия сохраняется, создается новая сессия, stale ttl указывает, как долго старая сессия может быть использована, например, 10 (в секундах).
idling_timeout 900 Таймаут бездействия указывает, как долго сессия может быть неактивной, прежде чем она будет считаться недействительной, например, 900 (15 минут) (в секундах), 0 отключает проверки и обновления.
rolling_timeout 3600 Таймаут обновления указывает, как долго сессия может использоваться, прежде чем ее необходимо обновить, например, 3600 (час) (в секундах), 0 отключает проверки и обновления.
absolute_timeout 86400 Абсолютный таймаут ограничивает, как долго сессия может быть обновлена, прежде чем потребуется повторная аутентификация, например, 86400 (сутки) (в секундах), 0 отключает проверки.
remember_rolling_timeout 604800 Таймаут запоминания указывает, как долго постоянная сессия считается действительной, например, 604800 (неделя) (в секундах), 0 отключает проверки и обновления.
remember_absolute_timeout 2592000 Абсолютный таймаут запоминания ограничивает, как долго постоянная сессия может быть обновлена, прежде чем потребуется повторная аутентификация, например, 2592000 (30 дней) (в секундах), 0 отключает проверки.
hash_storage_key false Указывать, хешировать ли ключ хранилища. С хешированным ключом хранилища невозможно расшифровать данные на стороне сервера без наличия cookie, используйте nil, true или false.
hash_subject false Указывать, хешировать ли тему, когда store_metadata включен, например, по причинам PII.
store_metadata false Указывать, нужно ли также хранить метаданные сессий, такие как сбор данных сессий для конкретной аудитории, принадлежащей конкретной теме.
touch_threshold 60 Порог обновления контролирует, как часто или редко session:refresh обновляет cookie, например, 60 (одна минута) (в секундах).
compression_threshold 1024 Порог сжатия контролирует, когда данные сжимаются, например, 1024 (килобайт) (в байтах), 0 отключает сжатие.
bind nil Привязать сессию к данным, полученным из HTTP-запроса или соединения, используйте ip, scheme, user-agent. Например, { "scheme", "user-agent" } будет рассчитывать MAC, используя также HTTP-запрос Scheme и заголовок User-Agent.
request_headers nil Набор заголовков для отправки на upstream, используйте id, audience, subject, timeout, idling-timeout, rolling-timeout, absolute-timeout. Например, { "id", "timeout" } установит заголовки запроса Session-Id и Session-Timeout, когда вызывается set_headers.
response_headers nil Набор заголовков для отправки downstream, используйте id, audience, subject, timeout, idling-timeout, rolling-timeout, absolute-timeout. Например, { "id", "timeout" } установит заголовки ответа Session-Id и Session-Timeout, когда вызывается set_headers.
storage nil Хранилище отвечает за хранение данных сессии, используйте nil или "cookie" (данные хранятся в cookie), "dshm", "file", "memcached", "mysql", "postgres", "redis" или "shm", или укажите имя пользовательского модуля ("custom-storage"), или таблицу, реализующую интерфейс хранения сессий.
dshm nil Конфигурация для хранилища dshm, например, { prefix = "sessions" } (см. ниже).
file nil Конфигурация для файлового хранилища, например, { path = "/tmp", suffix = "session" } (см. ниже).
memcached nil Конфигурация для хранилища memcached, например, { prefix = "sessions" } (см. ниже).
mysql nil Конфигурация для хранилища MySQL / MariaDB, например, { database = "sessions" } (см. ниже).
postgres nil Конфигурация для хранилища Postgres, например, { database = "sessions" } (см. ниже).
redis nil Конфигурация для хранилищ Redis / Redis Sentinel / Redis Cluster, например, { prefix = "sessions" } (см. ниже).
shm nil Конфигурация для хранилища общей памяти, например, { zone = "sessions" }
["custom-storage"] nil Конфигурация пользовательского хранилища (загружается с помощью require "custom-storage").

При хранении данных в cookie дополнительная конфигурация не требуется, просто установите storage в nil или "cookie".

Конфигурация хранилища DSHM

С хранилищем DHSM вы можете использовать следующие настройки (установите storage в "dshm"):

Параметр По умолчанию Описание
prefix nil Префикс для ключей, хранящихся в DSHM.
suffix nil Суффикс для ключей, хранящихся в DSHM.
host "127.0.0.1" Хост для подключения.
port 4321 Порт для подключения.
connect_timeout nil Управляет значением таймаута по умолчанию, используемым в методе connect объекта TCP/unix-domain socket.
send_timeout nil Управляет значением таймаута по умолчанию, используемым в методе send объекта TCP/unix-domain socket.
read_timeout nil Управляет значением таймаута по умолчанию, используемым в методе receive объекта TCP/unix-domain socket.
keepalive_timeout nil Управляет максимальным временем простоя соединений в пуле соединений.
pool nil Пользовательское имя для используемого пула соединений.
pool_size nil Размер пула соединений.
backlog nil Размер очереди, используемой, когда пул соединений полон (настраивается с помощью pool_size).
ssl nil Включить SSL.
ssl_verify nil Проверить сертификат сервера.
server_name nil Имя сервера для нового расширения TLS Server Name Indication (SNI).

Пожалуйста, обратитесь к ngx-distributed-shm для установки необходимых зависимостей.

Конфигурация файлового хранилища

С файловым хранилищем вы можете использовать следующие настройки (установите storage в "file"):

Параметр По умолчанию Описание
prefix nil Префикс файла для файла сессии.
suffix nil Суффикс файла (или расширение без .) для файла сессии.
pool nil Имя пула потоков, под которым происходит запись в файл (доступно только на Linux).
path (tmp directory) Путь (или директория), в которой создаются файлы сессий.

Реализация требует LuaFileSystem, который вы можете установить с помощью LuaRocks:

 luarocks install LuaFileSystem

Конфигурация хранилища Memcached

С файловым Memcached вы можете использовать следующие настройки (установите storage в "memcached"):

Параметр По умолчанию Описание
prefix nil Префикс для ключей, хранящихся в memcached.
suffix nil Суффикс для ключей, хранящихся в memcached.
host 127.0.0.1 Хост для подключения.
port 11211 Порт для подключения.
socket nil Файл сокета для подключения.
connect_timeout nil Управляет значением таймаута по умолчанию, используемым в методе connect объекта TCP/unix-domain socket.
send_timeout nil Управляет значением таймаута по умолчанию, используемым в методе send объекта TCP/unix-domain socket.
read_timeout nil Управляет значением таймаута по умолчанию, используемым в методе receive объекта TCP/unix-domain socket.
keepalive_timeout nil Управляет максимальным временем простоя соединений в пуле соединений.
pool nil Пользовательское имя для используемого пула соединений.
pool_size nil Размер пула соединений.
backlog nil Размер очереди, используемой, когда пул соединений полон (настраивается с помощью pool_size).
ssl false Включить SSL.
ssl_verify nil Проверить сертификат сервера.
server_name nil Имя сервера для нового расширения TLS Server Name Indication (SNI).

Конфигурация MySQL / MariaDB

С файловым MySQL / MariaDB вы можете использовать следующие настройки (установите storage в "mysql"):

Параметр По умолчанию Описание
host "127.0.0.1" Хост для подключения.
port 3306 Порт для подключения.
socket nil Файл сокета для подключения.
username nil Имя пользователя базы данных для аутентификации.
password nil Пароль для аутентификации, может потребоваться в зависимости от конфигурации сервера.
charset "ascii" Набор символов, используемый при подключении к MySQL.
database nil Имя базы данных для подключения.
table_name "sessions" Имя таблицы базы данных, в которую будут храниться данные сессий.
table_name_meta "sessions_meta" Имя таблицы метаданных базы данных, в которую будут храниться метаданные сессий.
max_packet_size 1048576 Верхний предел для ответных пакетов, отправляемых от сервера MySQL (в байтах).
connect_timeout nil Управляет значением таймаута по умолчанию, используемым в методе connect объекта TCP/unix-domain socket.
send_timeout nil Управляет значением таймаута по умолчанию, используемым в методе send объекта TCP/unix-domain socket.
read_timeout nil Управляет значением таймаута по умолчанию, используемым в методе receive объекта TCP/unix-domain socket.
keepalive_timeout nil Управляет максимальным временем простоя соединений в пуле соединений.
pool nil Пользовательское имя для используемого пула соединений.
pool_size nil Размер пула соединений.
backlog nil Размер очереди, используемой, когда пул соединений полон (настраивается с помощью pool_size).
ssl false Включить SSL.
ssl_verify nil Проверить сертификат сервера.

Вам также необходимо создать следующие таблицы в вашей базе данных:

--
-- Таблица базы данных, которая хранит данные сессий.
--
CREATE TABLE IF NOT EXISTS sessions (
  sid  CHAR(43) PRIMARY KEY,
  name VARCHAR(255),
  data MEDIUMTEXT,
  exp  DATETIME,
  INDEX (exp)
) CHARACTER SET ascii;

--
-- Таблица метаданных сессий.
--
-- Это необходимо только в том случае, если вы хотите хранить метаданные сессий.
--
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;

Конфигурация Postgres

С файловым Postgres вы можете использовать следующие настройки (установите storage в "postgres"):

Параметр По умолчанию Описание
host "127.0.0.1" Хост для подключения.
port 5432 Порт для подключения.
application 5432 Установите имя соединения, отображаемое в pg_stat_activity (по умолчанию "pgmoon").
username "postgres" Имя пользователя базы данных для аутентификации.
password nil Пароль для аутентификации, может потребоваться в зависимости от конфигурации сервера.
database nil Имя базы данных для подключения.
table_name "sessions" Имя таблицы базы данных, в которую будут храниться данные сессий (может быть префиксированным schema).
table_name_meta "sessions_meta" Имя таблицы метаданных базы данных, в которую будут храниться метаданные сессий (может быть префиксированным schema).
connect_timeout nil Управляет значением таймаута по умолчанию, используемым в методе connect объекта TCP/unix-domain socket.
send_timeout nil Управляет значением таймаута по умолчанию, используемым в методе send объекта TCP/unix-domain socket.
read_timeout nil Управляет значением таймаута по умолчанию, используемым в методе receive объекта TCP/unix-domain socket.
keepalive_timeout nil Управляет максимальным временем простоя соединений в пуле соединений.
pool nil Пользовательское имя для используемого пула соединений.
pool_size nil Размер пула соединений.
backlog nil Размер очереди, используемой, когда пул соединений полон (настраивается с помощью pool_size).
ssl false Включить SSL.
ssl_verify nil Проверить сертификат сервера.
ssl_required nil Прервать соединение, если сервер не поддерживает SSL-соединения.

Вам также необходимо создать следующие таблицы в вашей базе данных:

--
-- Таблица базы данных, которая хранит данные сессий.
--
CREATE TABLE IF NOT EXISTS sessions (
  sid  TEXT PRIMARY KEY,
  name TEXT,
  data TEXT,
  exp  TIMESTAMP WITH TIME ZONE
);
CREATE INDEX ON sessions (exp);

--
-- Таблица метаданных сессий.
--
-- Это необходимо только в том случае, если вы хотите хранить метаданные сессий.
--
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)
);

Реализация требует pgmoon, который вы можете установить с помощью LuaRocks:

 luarocks install pgmoon

Конфигурация Redis

Библиотека сессий поддерживает соединения с одиночным Redis, Redis Sentinel и Redis Cluster. Общие параметры конфигурации для всех:

Параметр По умолчанию Описание
prefix nil Префикс для ключей, хранящихся в Redis.
suffix nil Суффикс для ключей, хранящихся в Redis.
username nil Имя пользователя базы данных для аутентификации.
password nil Пароль для аутентификации.
connect_timeout nil Управляет значением таймаута по умолчанию, используемым в методе connect объекта TCP/unix-domain socket.
send_timeout nil Управляет значением таймаута по умолчанию, используемым в методе send объекта TCP/unix-domain socket.
read_timeout nil Управляет значением таймаута по умолчанию, используемым в методе receive объекта TCP/unix-domain socket.
keepalive_timeout nil Управляет максимальным временем простоя соединений в пуле соединений.
pool nil Пользовательское имя для используемого пула соединений.
pool_size nil Размер пула соединений.
backlog nil Размер очереди, используемой, когда пул соединений полон (настраивается с помощью pool_size).
ssl false Включить SSL.
ssl_verify nil Проверить сертификат сервера.
server_name nil Имя сервера для нового расширения TLS Server Name Indication (SNI).

Реализация single redis выбирается, когда вы не передаете ни sentinels, ни nodes, что приведет к выбору реализации sentinel или cluster.

Конфигурация одиночного Redis

Одиночный Redis имеет следующие дополнительные параметры конфигурации (установите storage в "redis"):

Параметр По умолчанию Описание
host "127.0.0.1" Хост для подключения.
port 6379 Порт для подключения.
socket nil Файл сокета для подключения.

Конфигурация Redis Sentinel

Redis Sentinel имеет следующие дополнительные параметры конфигурации (установите storage в "redis" и настройте sentinels):

Параметр По умолчанию Описание
master nil Имя мастера.
role nil "master" или "slave".
socket nil Файл сокета для подключения.
sentinels nil Redis Sentinels.
sentinel_username nil Необязательное имя пользователя sentinel.
sentinel_password nil Необязательный пароль sentinel.
database nil База данных для подключения.

sentinels — это массив записей Sentinel:

Параметр По умолчанию Описание
host nil Хост для подключения.
port nil Порт для подключения.

Реализация sentinel выбирается, когда вы передаете sentinels как часть конфигурации redis (и не передаете nodes, что выберет реализацию cluster).

Реализация требует lua-resty-redis-connector, который вы можете установить с помощью LuaRocks:

 luarocks install lua-resty-redis-connector

Конфигурация Redis Cluster

Redis Cluster имеет следующие дополнительные параметры конфигурации (установите storage в "redis" и настройте nodes):

Параметр По умолчанию Описание
name nil Имя кластера Redis.
nodes nil Узлы кластера Redis.
lock_zone nil Имя общей памяти для блокировок.
lock_prefix nil Префикс имени общей памяти для блокировок.
max_redirections nil Максимальное количество попыток для перенаправления.
max_connection_attempts nil Максимальное количество попыток для соединения.
max_connection_timeout nil Максимальное время ожидания соединения в целом среди попыток.

nodes — это массив записей узлов кластера:

Параметр По умолчанию Описание
ip "127.0.0.1" IP-адрес для подключения.
port 6379 Порт для подключения.

Реализация cluster выбирается, когда вы передаете nodes как часть конфигурации redis.

Для правильной работы cluster вам необходимо настроить lock_zone, поэтому добавьте это также в вашу конфигурацию Nginx:

lua_shared_dict redis_cluster_locks 100k;

И установите lock_zone в "redis_cluster_locks".

Реализация требует resty-redis-cluster или kong-redis-cluster, которые вы можете установить с помощью LuaRocks:

 luarocks install resty-redis-cluster
## или luarocks install kong-redis-cluster

Конфигурация SHM

С хранилищем SHM вы можете использовать следующие настройки (установите storage в "shm"):

Параметр По умолчанию Описание
prefix nil Префикс для ключей, хранящихся в SHM.
suffix nil Суффикс для ключей, хранящихся в SHM.
zone "sessions" Имя зоны общей памяти.

Вам также нужно создать общую словарную зону zone в Nginx:

lua_shared_dict sessions 10m;

Примечание: вам может потребоваться настроить размер зоны общей памяти в зависимости от ваших потребностей.

API

Сгенерированные LDoc API-документы также можно просмотреть по адресу bungle.github.io/lua-resty-session.

Инициализация

session.init

синтаксис: session.init(configuration)

Инициализирует библиотеку сессий.

Эта функция может быть вызвана на фазах init или init_worker в OpenResty, чтобы установить глобальную конфигурацию по умолчанию для всех экземпляров сессий, созданных этой библиотекой.

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

Смотрите конфигурацию для возможных параметров конфигурации.

Конструкторы

session.new

синтаксис: session = session.new(configuration)

Создает новый экземпляр сессии.

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

Смотрите конфигурацию для возможных параметров конфигурации.

Помощники

session.open

синтаксис: session, err, exists = session.open(configuration)

Это можно использовать для открытия сессии, и она либо вернет существующую сессию, либо новую сессию. Параметры возврата exists (логическое значение) указывают, была ли возвращена существующая или новая сессия. Параметр err (строка) содержит сообщение о том, почему открытие могло не удаться (функция все равно вернет session).

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

Смотрите конфигурацию для возможных параметров конфигурации.

session.start

синтаксис: session, err, exists, refreshed = session.start(configuration)

Это можно использовать для начала сессии, и она либо вернет существующую сессию, либо новую сессию. В случае существующей сессии сессия также будет обновлена (по мере необходимости). Параметр возврата exists (логическое значение) указывает, была ли возвращена существующая или новая сессия. Параметр refreshed (логическое значение) указывает, была ли успешной попытка обновления. Параметр err (строка) содержит сообщение о том, почему открытие или обновление могло не удаться (функция все равно вернет session).

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

Смотрите конфигурацию для возможных параметров конфигурации.

session.logout

синтаксис: ok, err, exists, logged_out = session.logout(configuration)

Выход из конкретной аудитории.

Один cookie сессии может быть разделен между несколькими аудиториями (или приложениями), поэтому необходимо иметь возможность выйти только из одной аудитории, сохраняя сессию для других аудиторий. Параметр возврата exists (логическое значение) указывает, существовала ли сессия. Параметр возврата logged_out (логическое значение) сигнализирует, если сессия существовала и была также завершена. Параметр err (строка) содержит причину, по которой сессия не существовала или почему выход не удался. Параметр ok (истинное значение) будет true, когда сессия существовала и была успешно завершена.

Когда существует только одна аудитория, это можно считать равным session.destroy.

Когда последняя аудитория выходит, cookie также будет уничтожен и недействителен на клиенте.

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

Смотрите конфигурацию для возможных параметров конфигурации.

session.destroy

синтаксис: ok, err, exists, destroyed = session.destroy(configuration)

Уничтожает всю сессию и очищает cookies.

Один cookie сессии может быть разделен между несколькими аудиториями (или приложениями), поэтому необходимо иметь возможность выйти только из одной аудитории, сохраняя сессию для других аудиторий. Параметр возврата exists (логическое значение) указывает, существовала ли сессия. Параметр возврата destroyed (логическое значение) сигнализирует, если сессия существовала и была также уничтожена. Параметр err (строка) содержит причину, по которой сессия не существовала или почему выход не удался. Параметр ok (истинное значение) будет true, когда сессия существовала и была успешно завершена.

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

Смотрите конфигурацию для возможных параметров конфигурации.

Методы экземпляра

session:open

синтаксис: ok, err = session:open()

Это можно использовать для открытия сессии. Она возвращает true, когда сессия была открыта и проверена. В противном случае она возвращает nil и сообщение об ошибке.

local session = require "resty.session".new()
local ok, err = session:open()
if ok then
  -- сессия существует

else
  -- сессия не существовала или была недействительна
end

session:save

синтаксис: ok, err = session:save()

Сохраняет данные сессии и выдает новый cookie сессии с новым идентификатором сессии. Когда remember включен, он также выдает новый постоянный cookie и, возможно, сохраняет данные в бэкенд-хранилище. Он возвращает true, когда сессия была сохранена. В противном случае он возвращает nil и сообщение об ошибке.

local session = require "resty.session".new()
session:set_subject("john")
local ok, err = session:save()
if not ok then
  -- ошибка при сохранении сессии
end

session:touch

синтаксис: ok, err = session:touch()

Обновляет время бездействия сессии, отправляя обновленный cookie сессии. Он отправляет только клиентский cookie и никогда не вызывает какие-либо API хранилища сессий на бэкенде. Обычно session:refresh используется для косвенного вызова этого. В случае ошибки он возвращает nil и сообщение об ошибке, в противном случае true.

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

session:refresh

синтаксис: ok, err = session:refresh()

Либо сохраняет сессию (создавая новый идентификатор сессии), либо обновляет сессию в зависимости от того, приближается ли таймаут обновления, что означает, что по умолчанию, когда 3/4 времени обновления истекло, это 45 минут с учетом времени обновления по умолчанию в один час. Обновление имеет порог, по умолчанию одну минуту, поэтому его можно пропустить в некоторых случаях (вы можете вызвать session:touch(), чтобы заставить его сработать). В случае ошибки он возвращает nil и сообщение об ошибке, в противном случае true.

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

Вышеуказанный код выглядит немного как помощник session.start().

session:logout

синтаксис: ok, err = session:logout()

Выход либо уничтожает сессию, либо просто очищает данные для текущей аудитории и сохраняет их (выход из текущей аудитории). В случае ошибки он возвращает nil и сообщение об ошибке, в противном случае true.

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

session:destroy

синтаксис: ok, err = session:destroy()

Уничтожает сессию и очищает cookies. В случае ошибки он возвращает nil и сообщение об ошибке, в противном случае true.

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

session:close

синтаксис: session:close()

Просто закрывает экземпляр сессии, чтобы он больше не мог использоваться.

local session = require "resty.session".new()
session:set_subject("john")
local ok, err = session:save()
if not ok then
  -- ошибка при сохранении сессии
end
session:close()

session:set_data

синтаксис: session:set_data(data)

Устанавливает данные сессии. data должен быть таблицей.

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

session:get_data

синтаксис: data = session:get_data()

Получает данные сессии.

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

синтаксис: session:set(key, value)

Устанавливает значение в сессии.

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

session:get

синтаксис: value = session:get(key)

Получает значение из сессии.

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

синтаксис: session:set_audience(audience)

Устанавливает аудиторию сессии.

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

session:get_audience

синтаксис: audience = session:get_audience()

Устанавливает тему сессии.

session:set_subject

синтаксис: session:set_subject(subject)

Устанавливает аудиторию сессии.

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

session:get_subject

синтаксис: subject = session:get_subject()

Получает тему сессии.

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

session:get_property

синтаксис: value = session:get_property(name)

Получает свойство сессии. Возможные имена свойств:

  • "id": 43 байта идентификатора сессии (такой же, как nonce, но закодированный в base64 url)
  • "nonce": 32 байта nonce (такой же, как идентификатор сессии, но в необработанных байтах)
  • "audience": Текущая аудитория сессии
  • "subject": Текущая тема сессии
  • "timeout": Ближайший таймаут (в секундах) (что осталось от него)
  • "idling-timeout": Таймаут бездействия сессии (в секундах) (что осталось от него)
  • "rolling-timeout": Таймаут обновления сессии (в секундах) (что осталось от него)
  • "absolute-timeout": Абсолютный таймаут сессии (в секундах) (что осталось от него)

Примечание: возвращаемое значение может быть nil.

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

session:set_remember

синтаксис: session:set_remember(value)

Включает или отключает постоянные сессии.

Во многих формах входа пользователю предоставляется возможность "запомнить меня". Вы можете вызвать эту функцию в зависимости от того, что выбрал пользователь.

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

синтаксис: remember = session:get_remember()

Получает состояние постоянных сессий.

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

синтаксис: ok, err = session:clear_request_cookie()

Изменяет заголовки запроса, удаляя связанные сессии cookies. Это полезно, когда вы используете библиотеку сессий на прокси-сервере и не хотите, чтобы cookies сессий передавались на upstream-сервис. В случае ошибки он возвращает nil и сообщение об ошибке, в противном случае true (что можно игнорировать).

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

session:set_headers

синтаксис: ok, err = session:set_headers(arg1, arg2, ...)

Устанавливает заголовки запроса и ответа на основе конфигурации. В случае ошибки он возвращает nil и сообщение об ошибке, в противном случае true (что можно игнорировать).

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

Когда вызывается без аргументов, он установит заголовки запроса, настроенные с помощью request_headers, и заголовки ответа, настроенные с помощью response_headers.

Смотрите конфигурацию для возможных имен заголовков.

session:set_request_headers

синтаксис: ok, err = session:set_request_headers(arg1, arg2, ...)

Устанавливает заголовки запроса. В случае ошибки он возвращает nil и сообщение об ошибке, в противном случае true (что можно игнорировать).

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

Когда вызывается без аргументов, он установит заголовки запроса, настроенные с помощью request_headers.

Смотрите конфигурацию для возможных имен заголовков.

session:set_response_headers

синтаксис: ok, err = session:set_response_headers(arg1, arg2, ...)

Устанавливает заголовки ответа. В случае ошибки он возвращает nil и сообщение об ошибке, в противном случае true (что можно игнорировать).

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

Когда вызывается без аргументов, он установит заголовки ответа, настроенные с помощью response_headers.

Смотрите конфигурацию для возможных имен заголовков.

session.info:set

синтаксис: session.info:set(key, value)

Устанавливает значение в хранилище информации сессии. Хранилище информации сессии может использоваться в сценариях, когда вы хотите хранить данные на стороне сервера, но не хотите создавать новую сессию и отправлять новый cookie сессии. Данные хранилища информации не учитываются при проверке тега аутентификации или кода аутентификации сообщения. Таким образом, если вы хотите использовать это для данных, которые необходимо шифровать, вам нужно зашифровать значение перед передачей его в эту функцию.

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

С хранилищем cookie это все еще работает, но тогда это почти то же самое, что и session:set.

session.info:get

синтаксис: value = session.info:get(key)

Получает значение из хранилища информации сессии.

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

session.info:save

синтаксис: ok, err = session.info:save()

Сохраняет информацию. Обновляет только хранилище на бэкенде. Не отправляет новый cookie (кроме хранилища cookie).

local session = require "resty.session".new()
session.info:set("last-access", ngx.now())
local ok, err = session.info:save()
[ HEADER -------------------------------------------------------------------------------------]
[ Type || Flags || SID || Created at || Rolling Offset || Size || Tag || Idling Offset || Mac ]
[ 1B   || 2B    || 32B || 5B         || 4B             || 3B   || 16B || 3B            || 16B ]

и

[ PAYLOAD --]
[ Data  *B  ]

Как HEADER, так и PAYLOAD кодируются в base64 url перед помещением в заголовок Set-Cookie. При использовании хранилища на стороне сервера PAYLOAD не помещается в cookie. С хранилищем cookie закодированный в base64 заголовок конкатенируется с закодированным в base64 полезным содержимым.

HEADER имеет фиксированный размер 82 байта в двоичном формате или 110 байт в закодированном в base64 формате.

Поля заголовка объясняются:

  • Type: число 1, упакованное в один байт в формате little endian (в настоящее время единственный поддерживаемый type).
  • Flags: упакованные бинарные флаги (short) в двухбайтовом формате little endian.
  • SID: 32 байта криптографически случайных данных (идентификатор сессии).
  • Created at: бинарно упакованные секунды от эпохи в формате little endian, усеченные до 5 байт.
  • Rolling Offset: бинарно упакованные секунды с момента создания в формате little endian (целое число).
  • Size: бинарно упакованный размер данных в трехбайтовом формате little endian.
  • Tag: 16 байт аутентификационного тега от шифрования AES-256-GCM данных.
  • Idling Offset: бинарно упакованные секунды с момента создания + rolling offset в формате little endian, усеченные до 3 байт.
  • Mac: 16 байт кода аутентификации сообщения заголовка.

Шифрование данных

  1. Начальный ключевой материал (IKM):
  2. получить IKM из secret, хешируя secret с помощью SHA-256, или
  3. использовать 32-байтный IKM, когда он передается в библиотеку с ikm
  4. Сгенерировать 32 байта криптографически случайного идентификатора сессии (sid)
  5. Получить 32-байтный ключ шифрования и 12-байтный вектор инициализации с помощью HKDF с использованием SHA-256 (в режиме FIPS используется PBKDF2 с SHA-256 вместо этого)
  6. Использовать HKDF extract для получения нового ключа из ikm, чтобы получить key (этот шаг можно выполнить только один раз для каждого ikm):
    • длина вывода: 32
    • хеш: "sha256"
    • ключ: <ikm>
    • режим: только извлечение
    • информация: ""
    • соль: ""
  7. Использовать HKDF expand для получения 44 байт output:
    • длина вывода: 44
    • хеш: "sha256"
    • ключ: <key>
    • режим: только расширение
    • информация: "encryption:<sid>"
    • соль: ""
  8. Первые 32 байта output являются ключом шифрования (aes-key), а последние 12 байт — вектором инициализации (iv)
  9. Шифровать plaintext (кодированный в JSON и, возможно, сжатый) с использованием AES-256-GCM, чтобы получить ciphertext и tag
  10. шифр: "aes-256-gcm"
  11. ключ: <aes-key>
  12. iv: <iv>
  13. plaintext: <plaintext>
  14. aad: используйте первые 47 байт header как aad, которые включают:
    1. Type
    2. Flags
    3. Идентификатор сессии
    4. Время создания
    5. Rolling Offset
    6. Размер данных

Существует вариация для cookies remember на шаге 3, где мы можем использовать PBKDF2 вместо HKDF, в зависимости от настройки remember_safety (мы также используем это в режиме FIPS). Настройки PBKDF2:

  • длина вывода: 44
  • хеш: "sha256"
  • пароль: <key>
  • соль: "encryption:<sid>"
  • итерации: <1000|10000|100000|1000000>
  • pkcs5: 1 (соответствует FIPS в нашем случае, но необходимо отключить проверки, основанные на SP800-132, такие как количество итераций, см. https://docs.openssl.org/master/man7/provider-kdf/#kdf-parameters)

Количество итераций основано на настройке remember_safety ("Low", "Medium", "High", "Very High"), если remember_safety установлено в "None", мы будем использовать HDKF, как указано выше.

Примечание: Для обратной совместимости мы отключили проверки соответствия SP800-132 в режиме FIPS. Эти проверки гарантируют, что длина соли составляет не менее 128 бит, длина производного ключа составляет не менее 112 бит, а количество итераций составляет не менее 1000. Эти проверки отключены по умолчанию в стандартном поставщике OpenSSL, но включены по умолчанию в поставщике FIPS.

  1. Получите 32-байтный аутентификационный ключ (mac_key) с помощью HKDF с использованием SHA-256 (в режиме FIPS используется PBKDF2 с SHA-256 вместо этого):
    1. Используйте HKDF extract для получения нового ключа из ikm, чтобы получить key (этот шаг можно выполнить только один раз для каждого ikm и повторно использовать при генерации ключа шифрования):
      • длина вывода: 32
      • хеш: "sha256"
      • ключ: <ikm>
      • режим: только извлечение
      • информация: ""
      • соль: ""
    2. Используйте HKDF expand для получения 32 байт mac-key:
      • длина вывода: 32
      • хеш: "sha256"
      • ключ: <key>
      • режим: только расширение
      • информация: "authentication:<sid>"
      • соль: ""
  2. Рассчитайте код аутентификации сообщения с помощью HMAC-SHA256:
  3. хеш: "sha256"
  4. ключ: <mac-key>
  5. сообщение: используйте первые 66 байт header, которые включают:
    1. Type
    2. Flags
    3. Идентификатор сессии
    4. Время создания
    5. Rolling Offset
    6. Размер данных
    7. Tag
    8. Idling Offset

Интерфейс пользовательского хранилища

Если вы хотите реализовать пользовательское хранилище, вам нужно реализовать следующий интерфейс:

---
-- <custom> backend for session library
--
-- @module <custom>


---
-- Storage
-- @section instance


local metatable = {}


metatable.__index = metatable


function metatable.__newindex()
  error("attempt to update a read-only table", 2)
end


---
-- Store session data.
--
-- @function instance:set
-- @tparam string name cookie name
-- @tparam string key session key
-- @tparam string value session value
-- @tparam number ttl session ttl
-- @tparam number current_time current time
-- @tparam[opt] string old_key old session id
-- @tparam string stale_ttl stale ttl
-- @tparam[opt] table metadata table of metadata
-- @treturn true|nil ok
-- @treturn string error message
function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata)
  -- NYI
end


---
-- Retrieve session data.
--
-- @function instance:get
-- @tparam string name cookie name
-- @tparam string key session key
-- @treturn string|nil session data
-- @treturn string error message
function metatable:get(name, key)
  -- NYI
end


---
-- Delete session data.
--
-- @function instance:delete
-- @tparam string name cookie name
-- @tparam string key session key
-- @tparam[opt] table metadata  session meta data
-- @treturn boolean|nil session data
-- @treturn string error message
function metatable:delete(name, key, current_time, metadata)
  -- NYI
end


local storage = {}


---
-- Constructors
-- @section constructors


---
-- Configuration
-- @section configuration


---
-- <custom> storage backend configuration
-- @field <field-name> TBD
-- @table configuration


---
-- Create a <custom> storage.
--
-- This creates a new shared memory storage instance.
--
-- @function module.new
-- @tparam[opt]  table   configuration  <custom> storage @{configuration}
-- @treturn      table                  <custom> storage instance
function storage.new(configuration)
  -- NYI
  -- return setmetatable({}, metatable)
end


return storage

Пожалуйста, проверьте существующие реализации для деталей. И, пожалуйста, сделайте pull-request, чтобы мы могли интегрировать это непосредственно в библиотеку для других пользователей.

GitHub

Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-session.