Perfect 提供了包括 PostgreSQL、MySQL、SQLite3 和 CouchDB 在内的各类数据库,并包括用于开发会话交互过程的临时内存管理机制,并很快会提供 MongoDB 和 Redis 为后台的会话管理。
会话管理是所有Web应用环境的基础程序,提供了验证、临时变量存储、交易数据等等,比如购物车数据模型。
基本的会话系统设计方案是每当用户通过浏览器“访问”网站时,服务器会给用户一个临时的票据(即token,传统翻译为“令牌”,本文采用“票据”这一中文名称)并暂时保存在服务器上,即一串特殊的序列号标识,用以区别每一个不同的客户,在服务器/浏览器之间互相传递。浏览器端一般使用cookie进行本地文件方式保存票据,或者通过JSON格式反复在服务器/浏览器之间互相传递。因此票据可以根据浏览器端的编程方式,采用cookie缓存或者采用“持有”的方式进行存储(比如嵌入脚本页面,在提交请求时附加票据——已经超出了本文的范围)
为了管理并区分来自同一IP地址的不同客户请求,会话有一个非常重要的概念,即票据的时效性。一旦用户失去交互的活跃性超过一定时间(即在一定时间内没有与服务器有信息交互),则票据就失效了。这种方式提高了数据的安全性。
Perfect 会话机制实现了存储每个会话的创建时间、最后一次访问时间,以及时效期限。如果“最后一次访问时间” 加上 “超时限制” 小于当前时间,则会话过期。
每个会话对象包含以下属性:
- token - 票据(令牌)识别码,即会话唯一识别码
- userid - 可选项,用户身份标识字符串
- created - 会话创建时的整型时间戳,单位是秒
- updated - 会话最后一次访问时的整型时间戳,单位是秒
- idle - 时效期限:整数,单位是秒。即允许客户在这个时间范围内处于不活动状态,在此期间不算过期。
- data - 数据:一个[String:Any]类型字典,可以JSON格式进行存储。用于存储简单的临时信息。
- ipaddress - 会话创建时客户端的 IPv4 或 IPv6 地址。可选项,用于校验会话安全性,防范会话欺诈。
- useragent - 会话创建时请求头数据部分的 User Agent 用户代理字符串。可选项,用于校验会话安全性,防范会话欺诈。
以下模块包含示范代码,每个范例都有各自的使用说明:
如果希望使用基于内存变量的会话管理,请在您的项目中修改Package.swift 文件并增加以下内容:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session.git", majorVersion: 3)
Redis:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-Redis.git", majorVersion: 3)
PostgreSQL:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-PostgreSQL.git", majorVersion: 3)
MySQL:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-MySQL.git", majorVersion: 3)
SQLite3:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-SQLite.git", majorVersion: 3)
CouchDB:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-CouchDB.git", majorVersion: 3)
MongoDB:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-MongoDB.git", majorVersion: 3)
SessionConfig
结构包含了可以自行定义的会话管理设置:
// 会话名称
// 这个名称也会成为浏览器cookie的名称
SessionConfig.name = "PerfectSession"
// 允许处于零活动连接的时间限制,单位是秒
// 86400 秒表示一天。
SessionConfig.idle = 86400
// 可选项,设置 Cookie作用域
SessionConfig.cookieDomain = "localhost"
// 可选项,锁定创建时用的 IP 地址,即在会话过程中不允许 IP 地址发生变化,否则视为欺诈。默认为 false
SessionConfig.IPAddressLock = true
// 可选项,锁定创建时所用的用户代理,即服务器在会话过程中不允许用户代理发生变化,否则视为欺诈。默认为 false
SessionConfig.userAgentLock = true
// 指定采用 CouchDB 作为内存管理后台
// 指定 CouchDB 内用于管理会话的数据库名称
SessionConfig.couchDatabase = "sessions"
// 指定采用 MongoDB 作为内存管理后台
// 指定 MongoDB 内用于管理会话的集合名称
SessionConfig.mongoCollection = "sessions"
如果要改变上述会话配置结构的变量值,则必须在定义驱动之前完成配置。
如果 SessionConfig.IPAddressLock
或 SessionConfig.userAgentLock
被设置为真,则服务器会在整个会话过程中不允许客户端更换 IP 地址和用户代理。
这种方式增强了会话安全性,能够在一定程度上防止会话标识字符串被盗用后形成会话欺诈。
每个会话管理机制都有其独特的驱动实现,并且为存储进行了优化,因此必须在服务器启动server.start()
调用之前设置会话驱动和相应的 HTTP 过滤器。
在您的服务器主进程 main.swift 中,只要是修改了 SessionConfig
的配置,请务必采取以下类似操作:
// 初始化服务器
let server = HTTPServer()
// 定义会话驱动
let sessionDriver = SessionMemoryDriver()
// 增加对应的过滤器,用于在路由请求之前和之后执行相关操作。
server.setRequestFilters([sessionDriver.requestFilter])
server.setResponseFilters([sessionDriver.responseFilter])
在 Perfect 架构中,过滤器相当于某种“中间件”。“请求”过滤器用于截获HTTP请求,并抽取票据标识代码,根据票据访问后台数据库并提取会话信息,然后把会话信息转交给服务器应用。而“响应”过滤器则是在服务器即将向浏览器写会数据之前,保存当前会话信息,并把票据更新、交回给浏览器。
详见以下“数据库驱动配置选项详解”。
过滤器会把会话的 token
(票据)、userid
(用户标识)和data
(用户数据)属性追加到request
对象中,并传递给所有处理器句柄。其中,userid
和data
这两个用户属性都是可以进行读写操作的,并会自动保存到响应过滤器中。
参考以下简单的处理句柄代码示范:
// 定义一个“索引”句柄
open static func indexHandlerGet(request: HTTPRequest, _ response: HTTPResponse) {
// 从 TurnstileCrypto 加密函数库中产生一个随机数
let rand = URandom()
// 增加一些用于展示的随机数
request.session.data[rand.secureToken] = rand.secureToken
// 展示如何将当前会话数据转储为一个 JSON 字符串
let dump = try? request.session.data.jsonEncodedString()
// 用于展示会话票据标识代码的简单HTML页面,以及当前会话数据的JSON字符串
let body = "<p>您的会话票据标识为:<code>\(request.session.token)</code></p><p>会话数据:<code>\(dump)</code></p>"
// 发回响应
response.setBody(string: body)
response.completed()
}
获取当前会话票据标识代码:
request.session.token
读取用户身份标识字符串:
// 读取:
request.session.userid
// 设置:
request.session.userid = "MyString"
访问会话数据:
// 读取:
request.session.data
// 写入
request.session.data["keyString"] = "Value"
request.session.data["keyInteger"] = 1
request.session.data["keyBool"] = true
// 读取特定内容
if let val = request.session.data["keyString"] as? String {
let keyString = val
}
修改Package.swift并增加依存关系:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-Redis.git", majorVersion: 3)
设置数据库服务器连接参数:
RedisSessionConnector.host = "localhost"
RedisSessionConnector.port = 5432
RedisSessionConnector.password = "secret"
创建会话驱动实例:
let sessionDriver = SessionRedisDriver()
修改Package.swift并增加依存关系:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-PostgreSQL.git", majorVersion: 3)
设置数据库服务器连接参数:
PostgresSessionConnector.host = "localhost"
PostgresSessionConnector.port = 5432
PostgresSessionConnector.username = "username"
PostgresSessionConnector.password = "secret"
PostgresSessionConnector.database = "mydatabase"
PostgresSessionConnector.table = "sessions"
创建会话驱动实例:
let sessionDriver = SessionPostgresDriver()
修改Package.swift并增加依存关系:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-MySQL.git", majorVersion: 3)
设置数据库服务器连接参数:
MySQLSessionConnector.host = "localhost"
MySQLSessionConnector.port = 3306
MySQLSessionConnector.username = "username"
MySQLSessionConnector.password = "secret"
MySQLSessionConnector.database = "mydatabase"
MySQLSessionConnector.table = "sessions"
创建会话驱动实例:
let sessionDriver = SessionMySQLDriver()
修改Package.swift并增加依存关系:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-SQLite.git", majorVersion: 3)
设置数据库服务器连接参数:
SQLiteConnector.db = "./SessionDB"
创建会话驱动实例:
let sessionDriver = SessionSQLiteDriver()
修改Package.swift并增加依存关系:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-CouchDB.git", majorVersion: 3)
配置用于保存会话的CouchDB数据库名称
SessionConfig.couchDatabase = "perfectsessions"
设置数据库服务器连接参数:
CouchDBConnection.host = "localhost"
CouchDBConnection.username = "username"
CouchDBConnection.password = "secret"
创建会话驱动实例:
let sessionDriver = SessionCouchDBDriver()
修改Package.swift并增加依存关系:
.Package(url:"https://github.com/PerfectlySoft/Perfect-Session-MongoDB.git", majorVersion: 3)
配置用于保存会话的Mongo数据库名称
SessionConfig.mongoCollection = "perfectsessions"
设置数据库服务器连接参数:
MongoDBConnection.host = "localhost"
MongoDBConnection.database = "perfect_testing"
创建会话驱动实例:
let sessionDriver = SessionMongoDBDriver()