请教如何使用 gorilla/sessions 实现多点登陆

2019-06-06 23:31:15 +08:00
 dt2vba
比如一个账户可以从四个地方登陆

我的解决办法是设置一个全局变量 var mapSessionStore=make(map[string][]*sessions.Session)

如果用户 A 第一次登陆成功,则把新创建的 session 添加至 mapSessionStore["用户 A"]。以此类推。同时启动一个协程,对于 mapSessionStore 存储的每个 session,循环执行 session.IsNew,以此检测是否过期或者销毁,并进行相应的处理。

但是 session.IsNew 不能检测 session 是否过期。似乎这个解决办法不可行。

非常期待你的帮助。
3484 次点击
所在节点    Go 编程语言
10 条回复
jedrek
2019-06-07 00:07:45 +08:00
将一次 request - response 当成一次 session 不可以解决吗?想不通为何一定要整个长 session
dt2vba
2019-06-07 00:31:23 +08:00
@jedrek 非常感谢你的提醒,晚安。
dt2vba
2019-06-07 11:47:31 +08:00
@jedrek 你好,可以再解释一下吗?
jedrek
2019-06-07 14:25:49 +08:00
你要做的其实是 http 的身份鉴定和令牌有效期。

大致流程是在登录或注册成功后,服务端签发一个令牌返回给客户端,令牌常见存放在 cookie 中。客户端在下次请求时携带令牌,服务端验证令牌来判别此次请求的有效性,若令牌有效,说明此次请求的用户就是其声明的身份。反之不能证明身份的就不能通过。

其中的核心就是令牌,令牌中包含两部分信息,数据和签名,由服务端负责签发和验证。

举一个最小化的例子. ID 为 10000 的用户登录成功了,服务端对 ID 进行数字签名。服务端将 ID + 签名结果 拼接起来组成字符串令牌放到 cookie 中返回给客户端。客户端下次请求时将令牌带上,服务端校验令牌格式和数字签名的有效性。签名的目的是防止伪造和篡改,比如数据部分被改成 10001,这个在服务端就不能通过验证。目前比较推荐的签名算法是 ed25519。

如果你需要限制令牌的有效期,原理也是一样的,将用户 ID 和截止时间一起签名返回给客户端。下次验证是否已过截止时间就可以了。

为了美观和统一, 可以将数据部分 base64 编码后再和签名拼接,用 . 号分隔。

不嫌烦的话 jwt 可以做到这个事,虽然它定义的标准很糟糕。

若要考虑修改密码后吊销所有令牌,在设计上又稍微复杂一点。
dt2vba
2019-06-07 17:17:27 +08:00
@jedrek
非常感谢你的详细的回复,尤其是关于令牌解释的部分,清晰地简述用户认证流程,令人印象深刻。

可能我对我的需求解释地不是很清楚。比如,在同一时刻,用户 A,在浏览器 1、2、3、4 登录在线。此时,用户 A 无法在浏览器 5 成功登录(因为最多允许 4 个)。如果用户 A 在浏览器 3 注销登录,那么用户 A 则可以在浏览器 5 成功登录。

但是对于用户注销登录状态,我不太确定令牌的方式是否可以实现。
dt2vba
2019-06-07 21:43:34 +08:00
@jedrek 非常感谢你的帮助,借助 dgrijalva/jwt-go,基本实现了需求。以下是我的解决方案。如果你有空的话,并且愿意提出一些意见,那将是非常期待的。

首先定义一个结构体,存储用户的 token 信息

type TokenManager struct {
Lock sync.Mutex

//map[user] map[token]user
Token map[string]map[string]string
}

然后定义一个该结构体的方法,删除已经过期的 token

func (manager *TokenManager) DeleteTokenNotValid() {
//删除过期的 Token
...

return
}

定义一个全局的 TokenManager 结构体变量
var TokenStore=TokenManager{}

主函数

func main() {
//开启协程,删除已经过期的 token
go func() {
for true {
TokenStore.DeleteTokenNotValid()
}
}()

//业务处理函数
...
}

登录处理函数

func login(c echo.Context) error {
//如果请求携带有效的 token,则跳转至首页
...

//如果用户的 token 数量大于等于 4,则禁止登录
...

//创建用户的 token,并保存至 TokenStore.Token[user]
...
}

注销处理函数

func logout(c echo.Context) error {
//删除请求携带的有效 token
...
}
jedrek
2019-06-07 21:51:54 +08:00
在上面的身份鉴定的基础上,另外使用一次性口令。

登录或注册成功后,服务端签发身份令牌和生成并保存新口令,然后将身份令牌和口令同时返回客户端,客户端将身份令牌和口令保存。下一次请求时,客户端携带口令和身份令牌,服务端做两个验证,一是请求的口令是否与已存在的匹配,二是身份令牌是否有效。两者都有效才通过。都验证通过后,返回时,生成新的口令返回给客户端(不需要更新身份令牌),服务端和客户端都需要更新保存新口令,下次请求同理。登出时删掉服务端对应的口令和客户端信息即可。

当第五个客户端登录时,服务端检测当前用户已有四个令牌了,拒绝登录。
当用户拷贝 http headers 试图突破限制时,第五个客户端接收了新的口令,原客户端的口令就会失效,所以还是四个客户端

可以使用随机数做口令,因为是一次性的,具有排他性,所以不需要考虑重放攻击。但是有可能和自己已存在的口令冲突,可以用时间戳+随机数做口令,不需要加密或签名
jedrek
2019-06-07 21:55:52 +08:00
我不确定 jwt 是否考虑了客户端拷贝 token 来实现更多客户端同时使用
jedrek
2019-06-07 22:19:12 +08:00
更正:
当第五个客户端登录时,服务端检测当前用户已有四个令牌了,拒绝登录。
当第五个客户端登录时,服务端检测当前用户已有四个口令了,拒绝登录。

令牌 -> 口令
dt2vba
2019-06-08 07:42:03 +08:00
@jedrek
我简单地试了一下,在已有四个客户端登录的情况下,复制任一有效的 jwt token,写入第五个客户端的 request header,的确可以登录。这时,已经有五个客户端处于在线状态。

我仔细地分析了一下,使用令牌和一次性口令,这个想法非常巧妙。似乎类似于 websocket 的心跳检测。

再次感谢你的帮助!

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/571680

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX