秋来冬风的博客

安全登录会话三版设计方案的演进历程

目录

此博客回顾我设计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定位至少一个与上一次登录差距太大
Tags:
Categories: