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,使用 true 或 false。 |
cookie_secure |
nil |
将 cookie 标记为安全,使用 nil、true 或 false。 |
cookie_priority |
nil |
Cookie 优先级,使用 nil、"Low"、"Medium" 或 "High"。 |
cookie_same_site |
"Lax" |
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" } 将利用 HTTP 请求的 Scheme 和 User-Agent 头来计算 MAC。 |
request_headers |
nil |
要发送到上游的头部集合,使用 id、audience、subject、timeout、idling-timeout、rolling-timeout、absolute-timeout。例如 { "id", "timeout" } 将在调用 set_headers 时设置 Session-Id 和 Session-Timeout 请求头。 |
response_headers |
nil |
要发送到下游的头部集合,使用 id、audience、subject、timeout、idling-timeout、rolling-timeout、absolute-timeout。例如 { "id", "timeout" } 将在调用 set_headers 时设置 Session-Id 和 Session-Timeout 响应头。 |
storage |
nil |
存储负责存储会话数据,使用 nil 或 "cookie"(数据存储在 cookie 中)、"dshm"、"file"、"memcached"、"mysql"、"postgres"、"redis" 或 "shm",或提供自定义模块的名称("custom-storage"),或一个实现会话存储接口的 table。 |
revocation |
nil |
用于 cookie 会话撤销记录的存储。使用 nil 或 false 禁用,存储名称如 "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 存储配置
当将数据存储到 cookie 时,不需要额外的配置,
只需将 storage 设置为 nil 或 "cookie"。
会话撤销配置
Cookie(无状态)会话是自包含的:一旦发出,cookie 在根据配置的超时过期之前一直有效。撤销添加了一个可选的基于存储的拒绝列表,以便被销毁的会话立即被拒绝,而无需等待 cookie 过期。
仅当会话数据存储在 cookie 中时(storage 为 nil 或 "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)的服务器名称。 |
当您不传递 sentinels 或 nodes 时,将选择 single redis 实现,否则将选择 sentinel 或 cluster 实现。
单个 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):
| 选项