lua-resty-session
OpenResty 的会话库 - 灵活且安全
$ opm get bungle/lua-resty-session
lua-resty-session
lua-resty-session 是一个安全且灵活的 OpenResty 会话库。
概括来说;
会话是不可变的(每次保存都会生成一个新的会话),并且是无锁的。
会话数据使用 HKDF-SHA256 派生的密钥进行 AES-256-GCM 加密。
会话具有一个固定大小的头部,该头部使用 HKDF-SHA256 派生的密钥进行 HMAC-SHA256 MAC 保护。
会话数据可以存储在无状态 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>Start the test</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>Session started (%s)</p>
<p><a href=/started>Check if it really was</a></p>
</body>
</html>
]], err or "no error"))
}
}
location /started {
content_by_lua_block {
local session, err = require "resty.session".start()
ngx.say(string.format([[
<html>
<body>
<p>Session was started by %s (%s)</p>
<p><blockquote>%s</blockquote></p>
<p><a href=/modify>Modify the session</a></p>
</body>
</html>
]],
session:get_subject() or "Anonymous",
err or "no error",
session:get("quote") or "no quote"
))
}
}
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>Session was modified (%s)</p>
<p><a href=/modified>Check if it is modified</a></p>
</body>
</html>
]], err or err_save or "no error"))
}
}
location /modified {
content_by_lua_block {
local session, err = require "resty.session".start()
ngx.say(string.format([[
<html>
<body>
<p>Session was started by %s (%s)</p>
<p><blockquote>%s</blockquote></p>
<p><a href=/destroy>Destroy the session</a></p>
</body>
</html>
]],
session:get_subject() or "Anonymous",
err or "no error",
session:get("quote") or "no quote"
))
}
}
location /destroy {
content_by_lua_block {
local ok, err = require "resty.session".destroy()
ngx.say(string.format([[
<html>
<body>
<p>Session was destroyed (%s)</p>
<p><a href=/destroyed>Check that it really was?</a></p>
</body>
</html>
]], err or "no error"))
}
}
location /destroyed {
content_by_lua_block {
local session, err = require "resty.session".open()
ngx.say(string.format([[
<html>
<body>
<p>Session was really destroyed, you are known as %s (%s)</p>
<p><a href=/>Start again</a></p>
</body>
</html>
]],
session:get_subject() or "Anonymous",
err or "no error"
))
}
}
}
}
安装
使用 OpenResty 包管理器 (opm)
❯ opm get bungle/lua-resty-session
lua-resty-session
的 OPM 仓库位于 https://opm.openresty.org.cn/package/bungle/lua-resty-session/。
还要检查每个存储的依赖项(可能存在其他依赖项)。
使用 LuaRocks
❯ luarocks install lua-resty-session
lua-resty-session
的 LuaRocks 仓库位于 https://luarocks.org/modules/bungle/lua-resty-session。
还要检查每个存储的依赖项(可能存在其他依赖项)。
配置
配置可以分为通用会话配置和服务器端存储配置。
以下是一个示例
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_http_only
| true
| 将 Cookie 标记为 HTTP Only,使用 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
| 保存会话时会创建一个新会话,过期时间指定旧会话还可以使用多长时间,例如: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
禁用压缩。 | | 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 存储配置
使用 DHSM 存储,您可以使用以下设置(将 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-domain 套接字对象 connect
方法使用的默认超时值。 | | send_timeout
| nil
| 控制 TCP/unix-domain 套接字对象 send
方法使用的默认超时值。 | | read_timeout
| nil
| 控制 TCP/unix-domain 套接字对象 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-domain 套接字对象 connect
方法使用的默认超时值。 | | send_timeout
| nil
| 控制 TCP/unix-domain 套接字对象 send
方法使用的默认超时值。 | | read_timeout
| nil
| 控制 TCP/unix-domain 套接字对象 receive
方法使用的默认超时值。 | | keepalive_timeout
| nil
| 控制连接池中连接的默认最大空闲时间。 | | pool
| nil
| 使用的连接池的自定义名称。 | | pool_size
| nil
| 连接池的大小。 | | backlog
| nil
| 当连接池已满时(使用 pool_size
配置)要使用的队列大小。 | | ssl
| false
| 是否启用 SSL。 | | ssl_verify
| nil
| 是否验证服务器证书。 |
您还需要在您的数据库中创建以下表格
`sql
-- 存储会话数据的数据库表。
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 Configuration
With file Postgres you can use the following settings (set the `storage` to `"postgres"`):
| Option | Default | Description |
|---------------------|:-----------------:|-----------------------------------------------------------------------------------------------------------|
| `host` | `"127.0.0.1"` | The host to connect. |
| `port` | `5432` | The port to connect. |
| `application` | `5432` | Set the name of the connection as displayed in pg_stat_activity (defaults to `"pgmoon"`). |
| `username` | `"postgres"` | The database username to authenticate. |
| `password` | `nil` | Password for authentication, may be required depending on server configuration. |
| `database` | `nil` | The database name to connect. |
| `table_name` | `"sessions"` | Name of database table to which to store session data (can be `database schema` prefixed). |
| `table_name_meta` | `"sessions_meta"` | Name of database meta data table to which to store session meta data (can be `database schema` prefixed). |
| `connect_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. |
| `send_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `send` method. |
| `read_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. |
| `keepalive_timeout` | `nil` | Controls the default maximal idle time of the connections in the connection pool. |
| `pool` | `nil` | A custom name for the connection pool being used. |
| `pool_size` | `nil` | The size of the connection pool. |
| `backlog` | `nil` | A queue size to use when the connection pool is full (configured with pool_size). |
| `ssl` | `false` | Enable SSL. |
| `ssl_verify` | `nil` | Verify server certificate. |
| `ssl_required` | `nil` | Abort the connection if the server does not support SSL connections. |
You also need to create following tables in your database:
```sql
--
-- Database table that stores session data.
--
CREATE TABLE IF NOT EXISTS sessions (
sid TEXT PRIMARY KEY,
name TEXT,
data TEXT,
exp TIMESTAMP WITH TIME ZONE
);
CREATE INDEX ON sessions (exp);
--
-- Sessions metadata table.
--
-- This is only needed if you want to store session metadata.
--
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-domain 套接字对象 connect
方法使用的默认超时值。 | | send_timeout
| nil
| 控制 TCP/unix-domain 套接字对象 send
方法使用的默认超时值。 | | read_timeout
| nil
| 控制 TCP/unix-domain 套接字对象 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 Sentinels 配置
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
| 要连接的端口。 |
当您将 sentinels
作为 redis
配置的一部分传递时(并且不传递 nodes
,这将选择 cluster
实现),将选择 sentinel
实现。
此实现需要 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
# or
❯ 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",
},
})
有关可能的配置设置,请参阅 configuration。
构造函数
session.new
语法: session = session.new(configuration)
创建一个新的会话实例。
local session = require "resty.session".new()
-- OR
local session = require "resty.session".new({
audience = "my-application",
})
有关可能的配置设置,请参阅 configuration。
辅助函数
session.open
语法: session, err, exists = session.open(configuration)
这可以用来打开一个会话,它将返回一个现有的会话或一个新的会话。exists
(布尔值)返回值参数指示它是返回的现有会话还是新会话。err
(字符串)包含打开可能失败的原因的消息(该函数也将返回 session
)。
local session = require "resty.session".open()
-- OR
local session, err, exists = require "resty.session".open({
audience = "my-application",
})
有关可能的配置设置,请参阅 configuration。
session.start
语法: session, err, exists, refreshed = session.start(configuration)
这可以用来启动一个会话,它将返回一个现有的会话或一个新的会话。如果存在现有会话,则会话也将刷新(根据需要)。exists
(布尔值)返回值参数指示它是返回的现有会话还是新会话。refreshed
(布尔值)指示对 refresh
的调用是否成功。err
(字符串)包含打开或刷新可能失败的原因的消息(该函数也将返回 session
)。
local session = require "resty.session".start()
-- OR
local session, err, exists, refreshed = require "resty.session".start({
audience = "my-application",
})
有关可能的配置设置,请参阅 configuration。
session.logout
语法: ok, err, exists, logged_out = session.logout(configuration)
它注销特定受众。
单个会话 Cookie 可能会在多个受众(或应用程序)之间共享,因此需要能够仅从单个受众注销,同时保留其他受众的会话。exists
(布尔值)返回值参数指示会话是否存在。logged_out
(布尔值)返回值参数表示会话是否存在以及是否已注销。err
(字符串)包含会话不存在或注销失败的原因。当会话存在并成功注销时,ok
(真值)将为true
。
当只有一个受众时,这可以被认为等同于session.destroy
。
当最后一个受众注销时,Cookie 也将被销毁并在客户端上失效。
require "resty.session".logout()
-- OR
local ok, err, exists, logged_out = require "resty.session".logout({
audience = "my-application",
})
有关可能的配置设置,请参阅 configuration。
session.destroy
语法: ok, err, exists, destroyed = session.destroy(configuration)
它销毁整个会话并清除 Cookie。
单个会话 Cookie 可能会在多个受众(或应用程序)之间共享,因此需要能够仅从单个受众注销,同时保留其他受众的会话。exists
(布尔值)返回值参数指示会话是否存在。destroyed
(布尔值)返回值参数表示会话是否存在以及是否已销毁。err
(字符串)包含会话不存在或注销失败的原因。当会话存在并成功注销时,ok
(真值)将为true
。
require "resty.session".destroy()
-- OR
local ok, err, exists, destroyed = require "resty.session".destroy({
cookie_name = "auth",
})
有关可能的配置设置,请参阅 configuration。
实例方法
session:open
语法: ok, err = session:open()
这可用于打开会话。当会话打开并验证时,它返回true
。否则,它返回nil
和错误消息。
local session = require "resty.session".new()
local ok, err = session:open()
if ok then
-- session exists
else
-- session did not exists or was invalid
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
-- error when saving session
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()
销毁会话并清除 Cookie。在错误情况下,它返回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
-- error when saving session
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
语法: session:clear_request_cookie()
通过删除与会话相关的 Cookie 来修改请求头。当您在代理服务器上使用会话库并且不希望会话 Cookie 被转发到上游服务时,这很有用。
local session, err, exists = require "resty.session".open()
if exists then
session:clear_request_cookie()
end
session:set_headers
语法: session:set_headers(arg1, arg2, ...)
根据配置设置请求和响应头。
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
语法: session:set_request_headers(arg1, arg2, ...)
设置请求头。
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
语法: session:set_response_headers(arg1, arg2, ...)
设置请求头。
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
语法: value = 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 ]
在放入Set-Cookie
头之前,HEADER
和PAYLOAD
都经过 base64 url 编码。当使用服务器端存储时,PAYLOAD
不会放入 Cookie 中。使用 Cookie 存储时,base64 url 编码的头与 base64 url 编码的有效负载连接。
HEADER
是固定大小的 82 字节二进制数据或 base64 url 编码形式的 110 字节。
头字段说明
类型:数字
1
以小端字节序打包在一个字节中(目前唯一支持的type
)。标志:以小端字节序打包的二进制标志(短整型),占用两个字节。
SID:
32
字节的加密随机数据(会话 ID)。创建时间:以小端字节序打包的从纪元开始的秒数,截断为 5 个字节。
滚动偏移量:以小端字节序打包的从创建时间开始的秒数(整数)。
大小:以小端字节序打包的数据大小(短整型),占用两个字节。
标签:
16
字节的数据 AES-256-GCM 加密的身份验证标签。空闲偏移量:以小端字节序打包的从创建时间 + 滚动偏移量开始的秒数,截断为 3 个字节。
Mac:
16
字节报文鉴别码,对头的报文鉴别码。
数据加密
初始密钥材料 (IKM)
通过对
secret
使用 SHA-256 哈希来派生secret
的 IKM,或者使用 32 字节 IKM,当使用
ikm
传递给库时
生成 32 字节的加密随机会话 ID (
sid
)使用 HKDF 和 SHA-256 派生 32 字节的加密密钥和 12 字节的初始化向量(在 FIPS 模式下,它使用 PBKDF2 和 SHA-256 代替)
使用 HKDF extract 从
ikm
派生一个新密钥以获取key
(此步骤每个ikm
可以只执行一次)输出长度:
32
摘要:
"sha256"
密钥:
<ikm>
模式:
仅提取
信息:
""
盐:
""
使用 HKDF expand 派生
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
创建时间
滚动偏移量
数据大小
步骤 3 中的remember
Cookie 有一个变体,我们可能会使用PBKDF2
而不是HKDF
,具体取决于remember_safety
设置(我们还在 FIPS 模式下使用它)。PBKDF2
设置
输出长度:
44
摘要:
"sha256"
密码:
<key>
盐:
"encryption:<sid>"
迭代次数:
<1000|10000|100000|1000000>
迭代次数基于remember_safety
设置("Low"
、"Medium"
、"High"
、"Very High"
),如果remember_safety
设置为"None"
,我们将使用上述 HDKF。
Cookie 头部身份验证
使用 HKDF 和 SHA-256 派生 32 字节的身份验证密钥 (
mac_key
)(在 FIPS 模式下,它使用 PBKDF2 和 SHA-256 代替)使用 HKDF extract 从
ikm
派生一个新密钥以获取key
(此步骤每个ikm
可以只执行一次并与加密密钥生成重复使用)输出长度:
32
摘要:
"sha256"
密钥:
<ikm>
模式:
仅提取
信息:
""
盐:
""
使用 HKDF expand 派生
32
字节的mac-key
输出长度:
32
摘要:
"sha256"
密钥:
<key>
模式:
仅扩展
信息:
"authentication:<sid>"
盐:
""
使用 HMAC-SHA256 计算消息身份验证代码
摘要:
"sha256"
密钥:
<mac-key>
消息:使用
header
的前 66 个字节,包括类型
标志
会话 ID
创建时间
滚动偏移量
数据大小
标签
空闲偏移量
自定义存储接口
如果您想实现自定义存储,则需要实现以下接口
`lua
-- <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 元数据表 -- @tparam boolean remember 是否存储持久会话 -- @treturn true|nil ok -- @treturn string 错误消息 function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember) -- 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
Please check the existing implementations for the defails. And please
make a pull-request so that we can integrate it directly to library
for other users as well.
# License
`lua-resty-session` uses two clause BSD license.
版权所有 (c) 2014 – 2023 Aapo Talvensaari,2022 – 2023 Samuele Illuminati 保留所有权利。
在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用,无论是否修改。
源代码的再分发必须保留上述版权声明、此条件列表以及以下免责声明。
二进制形式的再分发必须在随分发提供的文档和/或其他材料中复制上述版权声明、此条件列表以及以下免责声明。
本软件由版权持有人和贡献者“按原样”提供,并且不提供任何明示或暗示的担保,包括但不限于适销性和特定用途适用性的暗示担保。在任何情况下,版权持有人或贡献者均不对任何直接、间接、附带、特殊、惩戒性或后果性损害负责
作者
Aapo Talvensaari (@bungle),Samuele Illuminati (@samugi)
许可证
2bsd
依赖项
hamishforbes/lua-ffi-zlib >= 0.5,fffonion/lua-resty-openssl >= 0.8.0,luajit,nginx,ngx_http_lua
版本
-
bungle/lua-resty-session 4.0.5OpenResty 的会话库 - 灵活且安全 2023-08-16 15:38:02
-
bungle/lua-resty-session 4.0.4OpenResty 的会话库 - 灵活且安全 2023-06-05 14:29:06
-
bungle/lua-resty-session 4.0.3OpenResty 的会话库 - 灵活且安全 2023-02-21 19:24:59
-
bungle/lua-resty-session 4.0.2OpenResty 的会话库 - 灵活且安全 2023-02-15 15:13:41
-
bungle/lua-resty-session 4.0.1OpenResty 的会话库 - 灵活且安全 2023-02-05 07:44:02
-
bungle/lua-resty-session 4.0.0OpenResty 的会话库 - 灵活且安全 2023-02-01 20:05:10
-
OpenResty 的会话库 - 灵活且安全 2022-01-14 18:19:20
-
OpenResty 的会话库 - 灵活且安全 2022-01-14 13:33:54
-
OpenResty 的会话库 - 灵活且安全 2021-01-04 12:06:23
-
OpenResty 的会话库 - 灵活且安全 2020-10-27 18:53:47
-
OpenResty 的会话库 - 灵活且安全 2020-06-24 20:23:05
-
OpenResty 的会话库 - 灵活且安全 2020-05-22 10:49:55
-
OpenResty 的会话库 - 灵活且安全 2020-05-08 10:54:58
-
OpenResty 的会话库 - 灵活且安全 2020-05-05 21:51:00
-
OpenResty 的会话库 - 灵活且安全 2020-04-30 11:06:32
-
OpenResty 的会话库 - 灵活且安全 2020-03-28 09:47:22
-
OpenResty 的会话库 - 灵活且安全 2020-03-27 12:02:09
-
OpenResty 的会话库 - 灵活且安全 2020-02-11 13:05:36
-
OpenResty 的会话库 - 灵活且安全 2019-11-06 13:25:35
-
OpenResty 的会话库 - 灵活且安全 2019-07-05 09:58:23
-
OpenResty 的会话库 - 灵活且安全 2018-12-12 16:05:46
-
OpenResty 的会话库 - 灵活且安全 2018-03-16 23:31:02
-
OpenResty 的会话库 - 灵活且安全 2018-03-16 20:42:40
-
OpenResty 的会话库 - 灵活且安全 2017-09-19 15:26:18
-
OpenResty 的会话库 - 灵活且安全 2017-07-10 20:42:19
-
OpenResty 的会话库 - 灵活且安全 2017-06-12 09:53:47
-
OpenResty 的会话库 - 灵活且安全 2017-06-05 09:10:43
-
OpenResty 的会话库 - 灵活且安全 2017-02-13 17:52:07
-
OpenResty 的会话库 - 灵活且安全 2016-11-21 20:59:16
-
OpenResty 的会话库 - 灵活且安全 2016-11-21 17:52:19
-
OpenResty 的会话库 - 灵活且安全 2016-10-03 19:35:10
-
OpenResty 的会话库 - 灵活且安全 2016-09-29 20:01:52