跳转至

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.2.0, 该版本发布于 2026 年 8 月 24 日。


lua-resty-session 是一个用于 OpenResty 的安全、灵活的会话库。

快速概览;

  • 会话是不可变的(每次保存都会生成一个新会话),并且无需锁。
  • 会话数据使用 AES-256-GCM 加密,密钥通过 HKDF-SHA256 派生(在 FIPS 模式下,改用带 SHA-256 的 PBKDF2)。
  • 会话有一个固定大小的头部,使用 HMAC-SHA256 MAC 保护,密钥通过 HKDF-SHA256 派生(在 FIPS 模式下,改用带 SHA-256 的 PBKDF2)。
  • 会话数据可以存储在无状态 cookie 中,也可以存储在各种后端存储中。
  • 单个会话 cookie 可以在不同受众(audience)之间维护多个会话。

注意: 版本 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)(无需使用 secret),需要恰好 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,使用 truefalse
cookie_secure nil 将 cookie 标记为安全,使用 niltruefalse
cookie_priority nil Cookie 优先级,使用 nil"Low""Medium""High"
cookie_same_site "Lax" Cookie 同站点策略,使用 nil"Lax""Strict""None""Default"
cookie_same_party nil 使用同方标志标记 cookie,使用 niltruefalse
cookie_partitioned nil 使用分区标志标记 cookie,使用 niltruefalse
remember false 启用或禁用持久会话,使用 niltruefalse
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,则无法在服务器端解密数据,使用 niltruefalse
hash_subject false store_metadata 启用时,是否对主体进行哈希,例如出于 PII 原因。
store_metadata false 是否也存储会话的元数据,例如收集属于特定主体的特定受众的会话数据。
touch_threshold 60 触摸阈值控制 session:refresh 触摸 cookie 的频率,例如 60(一分钟)(秒)
compression_threshold 1024 压缩阈值控制何时对数据进行压缩,例如 1024(一千字节)(字节),0 禁用压缩。
bind nil 将会话绑定到从 HTTP 请求或连接获取的数据,使用 ipschemeuser-agent。例如 { "scheme", "user-agent" } 将利用 HTTP 请求的 SchemeUser-Agent 头来计算 MAC。
request_headers nil 要发送到上游的头部集合,使用 idaudiencesubjecttimeoutidling-timeoutrolling-timeoutabsolute-timeout。例如 { "id", "timeout" } 将在调用 set_headers 时设置 Session-IdSession-Timeout 请求头。
response_headers nil 要发送到下游的头部集合,使用 idaudiencesubjecttimeoutidling-timeoutrolling-timeoutabsolute-timeout。例如 { "id", "timeout" } 将在调用 set_headers 时设置 Session-IdSession-Timeout 响应头。
storage nil 存储负责存储会话数据,使用 nil"cookie"(数据存储在 cookie 中)、"dshm""file""memcached""mysql""postgres""redis""shm",或提供自定义模块的名称("custom-storage"),或一个实现会话存储接口的 table
revocation nil 用于 cookie 会话撤销记录的存储。使用 nilfalse 禁用,存储名称如 "shm""redis""mysql""postgres",自定义存储模块名称,或具有 set/get 方法的存储 table
revocation_fail_mode "open" 当撤销存储不可达时的行为,使用 "open"(视为未撤销)或 "closed"(拒绝会话)。
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"

会话撤销配置

Cookie(无状态)会话是自包含的:一旦发出,cookie 在根据配置的超时过期之前一直有效。撤销添加了一个可选的基于存储的拒绝列表,以便被销毁的会话立即被拒绝,而无需等待 cookie 过期。

仅当会话数据存储在 cookie 中时(storagenil"cookie"),撤销才可用。使用 revocation = "dshm""file""memcached""mysql""postgres""redis""shm" 显式选择后端。该后端使用其正常的配置部分以及用于会话数据的相同存储 set/get 契约。也支持自定义存储模块名称和预构建的存储表。设置 revocation = false 或将其留空将禁用撤销。

在每次 session:open 时,库会检查会话标识符是否被撤销。在 session:destroy 时,标识符会被写入所选存储,TTL 等于剩余的会话生命周期(滚动和绝对超时)。撤销标记是一个轻量级的哨兵;不存储会话负载。

使用 revocation_fail_mode 控制存储不可用时的行为:

  • "open"(默认):记录警告并将会话视为未撤销。 即使撤销写入失败,销毁仍会清除 cookie。
  • "closed":拒绝会话打开或销毁操作。

撤销适用于 session:destroy(以及 session:logout 当它销毁最后一个受众时)。它不会在 session:save(会话轮换)或部分 session:logout(多个受众)时撤销先前的会话标识符。轮换或部分注销后,先前的 cookie 在其 stale_ttl 或超时到期之前仍然可用。

示例:

-- Redis 拒绝列表
require("resty.session").init({
  storage = "cookie",
  revocation = "redis",
  redis = {
    host = "127.0.0.1",
    password = "secret",
    prefix = "sessions",
  },
})

-- 共享内存拒绝列表
require("resty.session").init({
  storage = "cookie",
  revocation = "shm",
  shm = {
    zone = "sessions",
    prefix = "revocations",
  },
})

-- MySQL 拒绝列表
require("resty.session").init({
  storage = "cookie",
  revocation = "mysql",
  mysql = {
    host = "127.0.0.1",
    database = "sessions",
    username = "session",
    password = "secret",
  },
})

同样的模式适用于 "dshm""file""memcached""postgres"

DSHM 存储配置

使用 DSHM 存储时,您可以使用以下设置(将 storage 设置为 "dshm"):

选项 默认值 描述
prefix nil 存储在 DSHM 中的键的前缀。
suffix nil 存储在 DSHM 中的键的后缀。
host "127.0.0.1" 要连接的主机。
port 4321 要连接的端口。
connect_timeout nil 控制 TCP/unix 域套接字对象的 connect 方法中使用的默认超时值。
send_timeout nil 控制 TCP/unix 域套接字对象的 send 方法中使用的默认超时值。
read_timeout nil 控制 TCP/unix 域套接字对象的 receive 方法中使用的默认超时值。
keepalive_timeout nil 控制连接池中连接的最大空闲时间。
pool nil 所使用的连接池的自定义名称。
pool_size nil 连接池的大小。
backlog nil 当连接池已满时使用的队列大小(通过 pool_size 配置)。
ssl nil 启用 SSL。
ssl_verify nil 验证服务器证书。
server_name nil 用于新 TLS 扩展服务器名称指示(SNI)的服务器名称。

请参考 ngx-distributed-shm 以安装必要的依赖。

文件存储配置

使用文件存储时,您可以使用以下设置(将 storage 设置为 "file"):

选项 默认值 描述
prefix nil 会话文件的前缀。
suffix nil 会话文件的后缀(或扩展名,不含 .)。
pool nil 文件写入发生的线程池名称(仅适用于 Linux)。
path (临时目录) 创建会话文件的路径(或目录)。

该实现需要 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 控制 TCP/unix 域套接字对象的 connect 方法中使用的默认超时值。
send_timeout nil 控制 TCP/unix 域套接字对象的 send 方法中使用的默认超时值。
read_timeout nil 控制 TCP/unix 域套接字对象的 receive 方法中使用的默认超时值。
keepalive_timeout nil 控制连接池中连接的最大空闲时间。
pool nil 所使用的连接池的自定义名称。
pool_size nil 连接池的大小。
backlog nil 当连接池已满时使用的队列大小(通过 pool_size 配置)。
ssl false 启用 SSL
ssl_verify nil 验证服务器证书
server_name nil 用于新 TLS 扩展服务器名称指示(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 控制 TCP/unix 域套接字对象的 connect 方法中使用的默认超时值。
send_timeout nil 控制 TCP/unix 域套接字对象的 send 方法中使用的默认超时值。
read_timeout nil 控制 TCP/unix 域套接字对象的 receive 方法中使用的默认超时值。
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 控制 TCP/unix 域套接字对象的 connect 方法中使用的默认超时值。
send_timeout nil 控制 TCP/unix 域套接字对象的 send 方法中使用的默认超时值。
read_timeout nil 控制 TCP/unix 域套接字对象的 receive 方法中使用的默认超时值。
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 控制 TCP/unix 域套接字对象的 connect 方法中使用的默认超时值。
send_timeout nil 控制 TCP/unix 域套接字对象的 send 方法中使用的默认超时值。
read_timeout nil 控制 TCP/unix 域套接字对象的 receive 方法中使用的默认超时值。
keepalive_timeout nil 控制连接池中连接的最大空闲时间。
pool nil 所使用的连接池的自定义名称。
pool_size nil 连接池的大小。
backlog nil 当连接池已满时使用的队列大小(通过 pool_size 配置)。
ssl false 启用 SSL
ssl_verify nil 验证服务器证书
server_name nil 用于新 TLS 扩展服务器名称指示(SNI)的服务器名称。

当您不传递 sentinelsnodes 时,将选择 single redis 实现,否则将选择 sentinelcluster 实现。

单个 Redis 配置

单个 Redis 有以下额外的配置选项(将 storage 设置为 "redis"):

选项 默认值 描述
host "127.0.0.1" 要连接的主机。
port 6379 要连接的端口。
socket nil 要连接的套接字文件。
database nil 要连接的数据库。

Redis Sentinel 配置

Redis Sentinel 有以下额外的配置选项(将 storage 设置为 "redis" 并配置 sentinels):

选项 默认值 描述
master nil 主节点名称。
role nil "master""slave"
socket nil 要连接的套接字文件。
sentinels nil Redis Sentinel 节点。
sentinel_username nil 可选的 sentinel 用户名。
sentinel_password nil 可选的 sentinel 密码。
database nil 要连接的数据库。

sentinels 是 Sentinel 记录的数组:

选项 默认值 描述
host nil 要连接的主机。
port nil 要连接的端口。

当您在 redis 配置中传递 sentinels(并且不传递 nodes,否则会选择 cluster 实现)时,将选择 sentinel 实现。

该实现需要 lua-resty-redis-connector,您可以使用 LuaRocks 安装:

 luarocks install lua-resty-redis-connector

Redis Cluster 配置

Redis Cluster 有以下额外的配置选项(将 storage 设置为 "redis" 并配置 nodes):

| 选项