跳转至

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仅,使用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 当会话被保存时会创建一个新会话,过期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" }将计算MAC,同时利用HTTP请求的SchemeUser-Agent头。
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
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存储配置

使用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)的服务器名称。

当您不传递sentinelsnodes时,选择单个redis实现,这将导致选择sentinelcluster实现。

单个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-clusterkong-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的initinit_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

语法: 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()
[ 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  ]

HEADERPAYLOAD在放入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字节的消息认证码。

数据加密

  1. 初始密钥材料(IKM):
  2. 通过使用SHA-256对secret进行哈希从secret派生IKM,或
  3. 当通过ikm传递给库时使用32字节IKM
  4. 生成32字节的加密随机会话ID(sid
  5. 使用HKDF通过SHA-256派生32字节加密密钥和12字节初始化向量(在FIPS模式下使用PBKDF2与SHA-256)
  6. 使用HKDF提取从ikm派生新密钥以获取key(此步骤每个ikm只需执行一次):
    • 输出长度:32
    • 摘要:"sha256"
    • 密钥:<ikm>
    • 模式:仅提取
    • 信息:""
    • 盐:""
  7. 使用HKDF扩展派生44字节的output
    • 输出长度:44
    • 摘要:"sha256"
    • 密钥:<key>
    • 模式:仅扩展
    • 信息:"encryption:<sid>"
    • 盐:""
  8. output的前32字节是加密密钥(aes-key),最后12字节是初始化向量(iv
  9. 使用AES-256-GCM加密plaintext(JSON编码并可选解压缩)以获得ciphertexttag
  10. 密码:"aes-256-gcm"
  11. 密钥:<aes-key>
  12. iv:<iv>
  13. 明文:<plaintext>
  14. aad:使用header的前47字节作为aad,包括:
    1. 类型
    2. 标志
    3. 会话ID
    4. 创建时间
    5. 滚动偏移量
    6. 数据大小

对于remember cookies,在第3步中可能使用PBKDF2而不是HKDF,具体取决于remember_safety设置(我们在FIPS模式下也使用它)。 PBKDF2设置:

迭代计数基于remember_safety设置("Low""Medium""High""Very High"),如果将remember_safety设置为"None",我们将使用HDKF,如上所述。

注意: 为了向后兼容,我们在FIPS模式下禁用了SP800-132合规性检查。此检查确保盐长度至少为128位,派生密钥长度至少为112位,并且迭代计数至少为1000。这些检查在OpenSSL的默认提供程序中默认禁用,但在FIPS提供程序中默认启用。

  1. 使用HKDF通过SHA-256派生32字节身份验证密钥(mac_key)(在FIPS模式下使用PBKDF2与SHA-256):
    1. 使用HKDF提取从ikm派生新密钥以获取key(此步骤可以在每个ikm上执行一次并在加密密钥生成中重用):
      • 输出长度:32
      • 摘要:"sha256"
      • 密钥:<ikm>
      • 模式:仅提取
      • 信息:""
      • 盐:""
    2. 使用HKDF扩展派生32字节的mac-key
      • 输出长度:32
      • 摘要:"sha256"
      • 密钥:<key>
      • 模式:仅扩展
      • 信息:"authentication:<sid>"
      • 盐:""
  2. 使用HMAC-SHA256计算消息认证码:
  3. 摘要:"sha256"
  4. 密钥:<mac-key>
  5. 消息:使用header的前66字节,包括:
    1. 类型
    2. 标志
    3. 会话ID
    4. 创建时间
    5. 滚动偏移量
    6. 数据大小
    7. 标签
    8. 空闲偏移量

自定义存储接口

如果您想实现自定义存储,您需要实现以下接口:

---
-- <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仓库中找到此模块的其他配置提示和文档。