session: NGINX模块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,于2025年11月24日发布。
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 Fan")
session:set("quote", "The quick brown fox jumps over the lazy dog")
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 Fan")
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" |
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 |
当会话被保存时会创建一个新会话,过期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 |
发送到上游的头集合,使用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。 |
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"。
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" |
存储会话数据的数据库表名称(可以是数据库模式前缀)。 |
table_name_meta |
"sessions_meta" |
存储会话元数据的数据库元数据表名称(可以是数据库模式前缀)。 |
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时,选择单个redis实现,这将导致选择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 Sentinel。 |
sentinel_username |
nil |
可选的sentinel用户名。 |
sentinel_password |
nil |
可选的sentinel密码。 |
database |
nil |
要连接的数据库。 |
sentinels是一个Sentinel记录的数组:
| 选项 | 默认值 | 描述 |
|---|---|---|
host |
nil |
要连接的主机。 |
port |
nil |
要连接的端口。 |
当您将sentinels作为redis配置的一部分传递时,选择sentinel实现(并且不传递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 |
要连接的端口。 |
当您将nodes作为redis配置的一部分传递时,选择cluster实现。
为了使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" |
共享内存区域的名称。 |
您还需要在Nginx中创建一个共享字典zone:
lua_shared_dict sessions 10m;
注意: 您可能需要根据需要调整共享内存区域的大小。
API
LDoc生成的API文档也可以在bungle.github.io/lua-resty-session查看。
初始化
session.init
语法: session.init(configuration)
初始化会话库。
此函数可以在OpenResty的init或init_worker阶段调用,以设置所有由此库创建的会话实例的全局默认配置。
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(布尔值)指示refresh调用是否成功。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()
保存会话数据并发出带有新会话ID的新会话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()
根据滚动超时是否接近,保存会话(创建新会话ID)或触摸会话,默认情况下,当滚动超时的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需要是一个table。
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字节会话ID(与nonce相同,但经过base64 URL编码)"nonce": 32字节nonce(与会话ID相同,但为原始字节)"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
session:clear_request_cookie
语法: ok, err = session:clear_request_cookie()
通过删除与会话相关的cookies来修改请求头。当您在代理服务器上使用会话库并且不希望将会话cookies转发到上游服务时,这非常有用。在错误情况下,它返回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()
Cookie格式
[ 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在放入Set-Cookie头之前都经过base64 URL编码。使用服务器端存储时,PAYLOAD不会放入cookie中。使用cookie存储时,base64 URL编码的头与base64 URL编码的有效负载连接在一起。
HEADER是固定大小的82字节二进制或110字节的base64 URL编码形式。
头字段解释:
- 类型:数字
1以单个小端字节打包(当前唯一支持的type)。 - 标志:以两个字节的小端形式打包的标志(短)。
- SID:
32字节的加密随机数据(会话ID)。 - 创建时间:以小端形式打包的自1970年1月1日以来的秒数,截断为5字节。
- 滚动偏移量:以小端形式打包的自创建时间以来的秒数(整数)。
- 大小:以三个字节的小端形式打包的数据大小。
- 标签:来自AES-256-GCM加密数据的
16字节身份验证标签。 - 空闲偏移量:以小端形式打包的自创建时间+滚动偏移量的秒数,截断为3字节。
- Mac:
16字节的消息认证码。
数据加密
- 初始密钥材料(IKM):
- 通过使用SHA-256对
secret进行哈希从secret派生IKM,或 - 当通过
ikm传递给库时使用32字节IKM - 生成32字节的加密随机会话ID(
sid) - 使用HKDF通过SHA-256派生32字节加密密钥和12字节初始化向量(在FIPS模式下使用PBKDF2与SHA-256)
- 使用HKDF提取从
ikm派生新密钥以获取key(此步骤每个ikm只需执行一次):- 输出长度:
32 - 摘要:
"sha256" - 密钥:
<ikm> - 模式:
仅提取 - 信息:
"" - 盐:
""
- 输出长度:
- 使用HKDF扩展派生
44字节的output:- 输出长度:
44 - 摘要:
"sha256" - 密钥:
<key> - 模式:
仅扩展 - 信息:
"encryption:<sid>" - 盐:
""
- 输出长度:
output的前32字节是加密密钥(aes-key),最后12字节是初始化向量(iv)- 使用AES-256-GCM加密
plaintext(JSON编码并可选解压缩)以获得ciphertext和tag - 密码:
"aes-256-gcm" - 密钥:
<aes-key> - iv:
<iv> - 明文:
<plaintext> - aad:使用
header的前47字节作为aad,包括:- 类型
- 标志
- 会话ID
- 创建时间
- 滚动偏移量
- 数据大小
对于remember cookies,在第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,如上所述。
注意: 为了向后兼容,我们在FIPS模式下禁用了SP800-132合规性检查。此检查确保盐长度至少为128位,派生密钥长度至少为112位,并且迭代计数至少为1000。这些检查在OpenSSL的默认提供程序中默认禁用,但在FIPS提供程序中默认启用。
Cookie头认证
- 使用HKDF通过SHA-256派生32字节身份验证密钥(
mac_key)(在FIPS模式下使用PBKDF2与SHA-256):- 使用HKDF提取从
ikm派生新密钥以获取key(此步骤可以在每个ikm上执行一次并在加密密钥生成中重用):- 输出长度:
32 - 摘要:
"sha256" - 密钥:
<ikm> - 模式:
仅提取 - 信息:
"" - 盐:
""
- 输出长度:
- 使用HKDF扩展派生
32字节的mac-key:- 输出长度:
32 - 摘要:
"sha256" - 密钥:
<key> - 模式:
仅扩展 - 信息:
"authentication:<sid>" - 盐:
""
- 输出长度:
- 使用HKDF提取从
- 使用HMAC-SHA256计算消息认证码:
- 摘要:
"sha256" - 密钥:
<mac-key> - 消息:使用
header的前66字节,包括:- 类型
- 标志
- 会话ID
- 创建时间
- 滚动偏移量
- 数据大小
- 标签
- 空闲偏移量
自定义存储接口
如果您想实现自定义存储,您需要实现以下接口:
---
-- <custom>后端会话库
--
-- @module <custom>
---
-- 存储
-- @section instance
local metatable = {}
metatable.__index = metatable
function metatable.__newindex()
error("尝试更新只读表", 2)
end
---
-- 存储会话数据。
--
-- @function instance:set
-- @tparam string name cookie名称
-- @tparam string key 会话键
-- @tparam string value 会话值
-- @tparam number ttl 会话ttl
-- @tparam number current_time 当前时间
-- @tparam[opt] string old_key 旧会话ID
-- @tparam string stale_ttl 过期ttl
-- @tparam[opt] table metadata 元数据表
-- @treturn true|nil ok
-- @treturn string 错误消息
function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata)
-- NYI
end
---
-- 检索会话数据。
--
-- @function instance:get
-- @tparam string name cookie名称
-- @tparam string key 会话键
-- @treturn string|nil 会话数据
-- @treturn string 错误消息
function metatable:get(name, key)
-- NYI
end
---
-- 删除会话数据。
--
-- @function instance:delete
-- @tparam string name cookie名称
-- @tparam string key 会话键
-- @tparam[opt] table metadata 会话元数据
-- @treturn boolean|nil 会话数据
-- @treturn string 错误消息
function metatable:delete(name, key, current_time, metadata)
-- NYI
end
local storage = {}
---
-- 构造函数
-- @section constructors
---
-- 配置
-- @section configuration
---
-- <custom>存储后端配置
-- @field <field-name> 待定
-- @table configuration
---
-- 创建一个<custom>存储。
--
-- 这会创建一个新的共享内存存储实例。
--
-- @function module.new
-- @tparam[opt] table configuration <custom>存储 @{configuration}
-- @treturn table <custom>存储实例
function storage.new(configuration)
-- NYI
-- return setmetatable({}, metatable)
end
return storage
请检查现有实现以获取详细信息。并请提交拉取请求,以便我们可以将其直接集成到库中供其他用户使用。
GitHub
您可以在nginx-module-session的GitHub仓库中找到此模块的其他配置提示和文档。