V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
dt2vba
V2EX  ›  Go 编程语言

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

  •  
  •   dt2vba · 2019-06-06 23:31:15 +08:00 · 3483 次点击
    这是一个创建于 1981 天前的主题,其中的信息可能已经有所发展或是发生改变。
    比如一个账户可以从四个地方登陆

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

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

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

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

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

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

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

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

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

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

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

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

    但是对于用户注销登录状态,我不太确定令牌的方式是否可以实现。
    dt2vba
        6
    dt2vba  
    OP
       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
        7
    jedrek  
       2019-06-07 21:51:54 +08:00   ❤️ 1
    在上面的身份鉴定的基础上,另外使用一次性口令。

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

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

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

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

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

    再次感谢你的帮助!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5519 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 03:37 · PVG 11:37 · LAX 19:37 · JFK 22:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.