安全登录会话三版设计方案的演进历程
目录
此博客回顾我设计safesession的过程。
第一版设计方案
我在2024年7月写了一个安全登录会话(safe Session),基本设计思想的在Session ID in Cookie Value的基础上增加了身份验证。
具体做法是,增加从user-agent获取的系统类型(windows等)从客户端ip获取的ip定位,每次使用安全登录会话先这样验证。
实践中还保存了创建时间和用户名,用于会话过期和简化获取用户名,Cookie设置Secure和HttpOnly,还有Domain为空,默认samesite为Lax,Path为/,确保安全。
后续改进
- 最终使用AES-256-GCM加密Cookie Value
- 增加从user-agent获取的设备型号和浏览器名称验证
- 增加CSRF_TOKEN字段给调用者实现CSRF防护
- 服务器只保存Session ID和创建时间,完整数据加密保存在Cookie
- 允许调用者自行做更多验证
这就是第一版设计方案的全部设计方案,数据结构定义如下:
// Session 表示一个登录会话。
type Session struct {
// ID 对每个登录会话是唯一的。
ID string `gorm:"primaryKey;type:char(64)"`
// CreateTime 是创建登录会话的时间。
CreateTime time.Time
// Ip 是创建登录会话时的ip信息。
Ip IPInfo `json:"-" gorm:"-:all"`
// CSRF_TOKEN 用来防范跨站请求伪造攻击。
CSRF_TOKEN string
// 下列字段是创建登录会话时的客户端设备信息,
// 和ip信息以及CSRF_TOKEN一起保存在客户浏览器,不在服务器保存。
Os, OsVersion string `json:"-" gorm:"-:all"`
Name string `json:"-" gorm:"-:all"`
Device string `json:"-" gorm:"-:all"`
Broswer string `json:"-" gorm:"-:all"`
}
// IPInfo 是ip信息。
type IPInfo struct {
// Country 是ip属地。
// 正确命名应该是Region,为了向后兼容,所以不修改。
Country string `json:"country"`
}
完整go代码实现2025年5月开源在github(safesession)。
第二版设计方案
第一版设计方案的问题在于,存在这样的场景,如果Cookie Value泄露,使用免费ip定位数据库,在中国大陆,可能从user-agent和ip定位获取到的信息,都是ip定位China,系统Windows NT 10.0,实际仅有浏览器名发挥作用。
具体设计在开源仓库的自述文件,即增加验证验证ip的网络运营商、ip的ASN类型、浏览器指纹。
2025年5月写完自述文件。
第三版设计方案
上述实现未考虑到重复登录时的场景,所以修改CreateTime字段为上一次登录的时间,但为了数据库表不在生产环境修改,不修改字段名;这样实现了一次登录,登录会话自动续期。
第一版设计方案存在较少见的误判场景,第二版设计方案引入了一些比较常见的误判场景,如自述文件 常见问题章节提到的4个导致登录会话失效的场景。
第三版设计方案,将需要的验证信息规定为下列:
- ip定位(Country、Region、City、经度、纬度)
- gps定位(经度、纬度)
- AS号
- isp名
- 屏幕宽高
- 浏览器名
- 系统类型
- 系统版本
- 逻辑处理器数量
- 用户指纹(浏览器指纹或设备指纹)
验证信息出现下列情况默认登录会话失效
- 系统类型、浏览器名、与上一次登录不一致
用户指纹与上一次登录不一致且
- isp名、AS号至少一个与上一次登录不一样
- 逻辑处理器数量与上一次登录不一样
- 系统版本与上一次登录不一样
- 屏幕宽高与上一次登录不一样
- ip定位、gps定位至少一个与上一次登录差距太大