如何在不不断传递用户名/密码的情况下识别 Go 服务器上的用户

Posted

技术标签:

【中文标题】如何在不不断传递用户名/密码的情况下识别 Go 服务器上的用户【英文标题】:How to identify a user on a Go server without constantly passing username/password 【发布时间】:2018-04-16 20:39:03 【问题描述】:

我对网络服务器的东西还是新手,边走边学(请原谅双关语)。但是我有点困惑如何在不每次都询问用户名和密码的情况下维护发出 HTTP 请求的用户的身份。

我的客户端实际上是在 C# 中(它是一个电脑游戏),我的服务器将接收来自应用程序的 http 请求。我可以在登录后发送用户 ID,但任何人都可以发送用户 ID 并假装自己不是,所以这不是安全问题吗?

我不想一直发送用户/通行证,因为敏感信息会迫使我一直使用 HTTPS。

对此的一般解决方案是什么?我认为无法完成 cookie,因为它的 C# 应用程序而不是 Chrome 等浏览器。

【问题讨论】:

【参考方案1】:

我认为无法完成 cookie,因为它的 C# 应用程序而不是 Chrome 等浏览器。

Cookie 只是一个名为 Cookie 的标头,对于任何合理的 HTTP 库来说都不是问题。

我不想一直发送用户/通行证,因为敏感信息会迫使我一直使用 HTTPS。

这就是 10 年前 Facebook、Twitter 和大多数其他网站所做的。安全登录页面可防止您的凭据被盗,但您点击的其他不安全端点会泄露您用于身份验证的任何其他信息。这允许任何监视您的不安全连接的人冒充您的用户。见Firesheep。

由于您应该安全地存储您的用户密码,我想为每个请求验证您的用户密码很慢。

如果保证一个帐户一次不会被多个客户端使用,您可以为每个用户生成一个随机的 API 令牌,而与他们的用户名和密码无关。否则,您将不得不在用户登录时生成随机会话令牌,并将它们映射到具有某种数据库的实际用户。这是大多数网站处理登录的方式。

无论您做什么,如果某人知道自己的秘密(用户名+密码或会话 ID),就无法防止他们冒充另一个帐户。这就是为什么您必须使用 HTTPS 而不仅仅是 HTTP。

【讨论】:

所以所有请求/响应都应该使用 HTTPS,即使数据不敏感但需要用户发送身份令牌? @Sir:是的。令牌仍然是敏感的。不如用户名和密码(可能在其他网站/应用程序上重复使用),但仍然敏感,因为它是您用来验证用户身份的。 好的,谢谢 :) 那我还不如放弃 HTTP!【参考方案2】:

我认为典型的方法是使用会话 ID:https://en.wikipedia.org/wiki/Session_ID

【讨论】:

是什么阻止了一些人在他们的 json 中发送该会话 id 假装是另一个玩家?服务器怎么知道区别? @Sir 服务器不能。出于这个原因,session_id 必须很长并且使用安全的 PRNG 生成。 @Sir 有 2 个选项。您将那么长的Sessio_ID 存储在您的服务器上并尝试查找,如果没有找到 - 它是无效的。或者使用 JSON Web Tokens jwt.io - 这样它就用你的密钥签名了。【参考方案3】:

您不需要每次客户端完成的每个请求都传递用户名和密码。有办法做到这一点。

    您可以使用 cookie 来执行此操作。为此,您可以在服务器中发生成功登录时制作 cookie 并将它们传递给您的客户端。数据将保存在客户端,然后您的服务器将读取 cookie。

    但在您的情况下,您不能使用 cookie,因为您使用 C# 作为客户端。因此,您需要使用下面列出的其他方式

    您可以使用会话身份验证管理。会话是使任何登录安全可靠的最佳方式。为此,您必须生成一个会话密钥,然后将其用于验证经过身份验证的用户。这将在客户端登录时起作用,然后将生成一个新会话,这样您就可以阻止创建的其他会话。

以下是您可以参考的代码:

    登录功能

    func login(w http.ResponseWriter, r *http.Request) 
        sess := globalSessions.SessionStart(w, r)
        r.ParseForm()
        if r.Method == "GET" 
            t, _ := template.ParseFiles("login.gtpl")
            w.Header().Set("Content-Type", "text/html")
            t.Execute(w, sess.Get("username"))
         else 
            sess.Set("username", r.Form["username"])
            http.Redirect(w, r, "/", 302)
        
    
    

    创建会话

    func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) 
        manager.lock.Lock()
        defer manager.lock.Unlock()
        cookie, err := r.Cookie(manager.cookieName)
        if err != nil || cookie.Value == "" 
            sid := manager.sessionId()
            session, _ = manager.provider.SessionInit(sid)
            cookie := http.CookieName: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)
            http.SetCookie(w, &cookie)
         else 
            sid, _ := url.QueryUnescape(cookie.Value)
            session, _ = manager.provider.SessionRead(sid)
        
        return
    
    func (manager *Manager) sessionId() string 
        b := make([]byte, 32)
        if _, err := io.ReadFull(rand.Reader, b); err != nil 
            return ""
        
        return base64.URLEncoding.EncodeToString(b)
    
    

    删除会话

    func init() 
        go globalSessions.GC()
    
    
    func (manager *Manager) GC() 
        manager.lock.Lock()
        defer manager.lock.Unlock()
        manager.provider.SessionGC(manager.maxlifetime)
        time.AfterFunc(time.Duration(manager.maxlifetime), func()  manager.GC() )
    
    

参考:https://astaxie.gitbooks.io/build-web-application-with-golang/en/06.2.html

【讨论】:

以上是关于如何在不不断传递用户名/密码的情况下识别 Go 服务器上的用户的主要内容,如果未能解决你的问题,请参考以下文章

如何在不覆盖 TTY 的情况下将密码传递给 su/sudo/ssh?

如何在不连接 Powershell 的情况下传递参数? [复制]

如何在不知道 C 中的旧密码的情况下设置用户帐户密码?

如何在不提供用户名和密码的情况下访问共享文件夹[关闭]

如何在不将密码保存在数据库中的情况下验证用户身份

在 Meteor 中验证用户密码