人们如何在 Go 中管理身份验证? [关闭]

Posted

技术标签:

【中文标题】人们如何在 Go 中管理身份验证? [关闭]【英文标题】:How are people managing authentication in Go? [closed] 【发布时间】:2014-10-02 20:13:33 【问题描述】:

对于那些在 Go 中构建 RESTful API 和 JS 前端应用程序的人,您如何管理身份验证?您是否使用任何特定的库或技术?

我很惊讶发现关于这个的讨论如此之少。我牢记以下答案,并试图避免开发自己的实现:

Authentication Form in ASP.Net

每个人都分别编写自己的解决方案吗?

【问题讨论】:

身份验证很大程度上取决于您所追求的应用程序类型。没有一种万能的解决方案。此外,这是一个很难解决的问题。这可能就是您找不到任何确凿文档的原因。 您好,感谢您的快速回复。可以理解,但大多数语言和框架都提供了身份验证解决方案,涵盖了大多数应用程序共享的最常见身份验证要求,并获得了广泛的社区参与和支持。我同意这是一个难题。那些不是从合作努力中受益最多吗? (这不是抱怨,因为这是开源的,但更多的是观察到我们都在重新发明***。:) @jimt 事实上,这是一个难题,因此为我们凡人提供一个我们不会出错的锥形解决方案变得更加重要。 我投票结束这个问题,因为它是一个投票问题。 【参考方案1】:

老实说,您可以将许多身份验证方法和技术安装到您的应用程序中,这取决于应用程序的业务逻辑和要求。 比如Oauth2、LDAP、本地认证等 我的回答假设您正在寻找本地身份验证,这意味着您在应用程序中管理用户的身份。 服务器必须公开一组外部 API 允许用户和管理员 管理帐户以及他们希望如何向服务器标识自己以实现可信赖的通信。 您最终将创建一个包含用户信息的数据库表。 出于安全目的对密码进行哈希处理的地方请参阅How to store the password in the database

假设应用要求基于以下方法之一对用户进行身份验证:

基本身份验证(用户名、密码): 此身份验证方法取决于以 base64 编码并在rfc7617 中定义的 Authorization 标头中的用户凭据集,基本上,当应用程序接收到用户请求时,它会解码授权并重新散列密码以在 DB 散列中进行比较,如果它与用户匹配否则返回 401 状态码给用户。

基于证书的身份验证: 此身份验证方法依赖于数字证书来识别用户, 它被称为 x509 身份验证,因此当应用收到用户请求时,它会读取客户端的证书并验证它与提供给应用的 CA 根证书是否匹配。

不记名令牌: 这种身份验证方法依赖于短期访问令牌,承载令牌是一个神秘的字符串,通常由服务器响应登录请求而生成。因此,当应用收到用户请求时,它会读取授权并验证令牌以对用户进行身份验证。

不过,我推荐go-guardian 对于身份验证库,它通过一组称为策略的可扩展身份验证方法进行。基本上,Go-Guardian 不会挂载路由或假设任何特定的数据库模式,从而最大限度地提高灵活性并允许开发人员做出决策。

设置 go-guardian 身份验证器非常简单。

这里是上述方法的完整示例。

package main

import (
    "context"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "sync"

    "github.com/golang/groupcache/lru"
    "github.com/gorilla/mux"
    "github.com/shaj13/go-guardian/auth"
    "github.com/shaj13/go-guardian/auth/strategies/basic"
    "github.com/shaj13/go-guardian/auth/strategies/bearer"
    gx509 "github.com/shaj13/go-guardian/auth/strategies/x509"
    "github.com/shaj13/go-guardian/store"
)

var authenticator auth.Authenticator
var cache store.Cache

func middleware(next http.Handler) http.HandlerFunc 
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) 
        log.Println("Executing Auth Middleware")
        user, err := authenticator.Authenticate(r)
        if err != nil 
            code := http.StatusUnauthorized
            http.Error(w, http.StatusText(code), code)
            return
        
        log.Printf("User %s Authenticated\n", user.UserName())
        next.ServeHTTP(w, r)
    )


func Resource(w http.ResponseWriter, r *http.Request) 
    w.Write([]byte("Resource!!\n"))


func Login(w http.ResponseWriter, r *http.Request) 
    token := "90d64460d14870c08c81352a05dedd3465940a7"
    user := auth.NewDefaultUser("admin", "1", nil, nil)
    cache.Store(token, user, r)
    body := fmt.Sprintf("token: %s \n", token)
    w.Write([]byte(body))


func main() 
    opts := x509.VerifyOptions
    opts.KeyUsages = []x509.ExtKeyUsagex509.ExtKeyUsageClientAuth
    opts.Roots = x509.NewCertPool()
    // Read Root Ca Certificate
    opts.Roots.AddCert(readCertificate("<root-ca>"))

    cache = &store.LRU
        lru.New(100),
        &sync.Mutex,
    

    // create strategies
    x509Strategy := gx509.New(opts)
    basicStrategy := basic.New(validateUser, cache)
    tokenStrategy := bearer.New(bearer.NoOpAuthenticate, cache)

    authenticator = auth.New()
    authenticator.EnableStrategy(gx509.StrategyKey, x509Strategy)
    authenticator.EnableStrategy(basic.StrategyKey, basicStrategy)
    authenticator.EnableStrategy(bearer.CachedStrategyKey, tokenStrategy)

    r := mux.NewRouter()
    r.HandleFunc("/resource", middleware(http.HandlerFunc(Resource)))
    r.HandleFunc("/login", middleware(http.HandlerFunc(Login)))

    log.Fatal(http.ListenAndServeTLS(":8080", "<server-cert>", "<server-key>", r))


func validateUser(ctx context.Context, r *http.Request, userName, password string) (auth.Info, error) 
    // here connect to db or any other service to fetch user and validate it.
    if userName == "***" && password == "***" 
        return auth.NewDefaultUser("***", "10", nil, nil), nil
    

    return nil, fmt.Errorf("Invalid credentials")


func readCertificate(file string) *x509.Certificate 
    data, err := ioutil.ReadFile(file)

    if err != nil 
        log.Fatalf("error reading %s: %v", file, err)
    

    p, _ := pem.Decode(data)
    cert, err := x509.ParseCertificate(p.Bytes)
    if err != nil 
        log.Fatalf("error parseing certificate %s: %v", file, err)
    

    return cert


用法:

获取令牌:
curl  -k https://127.0.0.1:8080/login -u ***:***
token: 90d64460d14870c08c81352a05dedd3465940a7

使用令牌进行身份验证:
curl  -k https://127.0.0.1:8080/resource -H "Authorization: Bearer 90d64460d14870c08c81352a05dedd3465940a7"

Resource!!
使用用户凭据进行身份验证:
curl  -k https://127.0.0.1:8080/resource -u ***:***

Resource!!
使用用户证书进行身份验证:
curl --cert client.pem --key client-key.pem --cacert ca.pem https://127.0.0.1:8080/resource

Resource!!

您可以同时启用多种身份验证方法。您通常应该至少使用两种方法

【讨论】:

【参考方案2】:

看看Labstack Echo - 它将 RESTful API 和前端应用程序的身份验证封装到中间件中,您可以使用它来保护特定的 API 路由。

例如,设置基本身份验证就像为/admin 路由创建新的子路由器一样简单:

e.Group("/admin").Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) 
    if username == "joe" && password == "secret" 
        return true, nil
    
    return false, nil
))

See all of Labstack's middleware authentication options here.

【讨论】:

【参考方案3】:

在 2018 年回答这个问题。我建议使用 JWT(JSON Web Token)。您标记为已解决的答案有缺点,即前(用户)和后(服务器/数据库)的行程。更糟糕的是,如果用户频繁发出需要身份验证的请求,将导致来自/到服务器和数据库的请求臃肿。为了解决这个问题,使用 JWT 将令牌存储在用户端,用户可以在需要访问/请求时随时使用。无需访问数据库和服务器处理即可检查令牌的有效性需要很短的时间。

【讨论】:

【参考方案4】:

另一个可能的解决方案是Authboss,最近在邮件list 上宣布。

(我没有尝试过使用这个库。)

另见Best way to make a webapp with user auth?

【讨论】:

【参考方案5】:

另一个使用 cookie 处理身份验证的开源包是 httpauth。

(顺便说一句,我写的)

【讨论】:

【参考方案6】:

这个问题获得了大量的关注——并且有一个热门问题徽章——所以我知道这个话题有很多潜在的兴趣,而且很多人都在问同样的问题,但在互联网上找不到答案.

大部分可用信息都产生了与手部波动的文本等效的文本,作为“读者练习”。 ;)

但是我终于找到了一个具体的例子,(慷慨地)由 golang-nuts 邮件列表的成员提供:

https://groups.google.com/forum/#!msg/golang-nuts/GE7a_5C5kbA/fdSnH41pOPYJ

这提供了建议的架构和服务器端实现,作为自定义身份验证的基础。客户端代码仍由您决定。

(希望帖子的作者看到这个:谢谢!)

摘录(并重新格式化):


“我建议如下设计:

create table User (
 ID int primary key identity(1,1),
 Username text,
 FullName text,
 PasswordHash text,
 PasswordSalt text,
 IsDisabled bool
)

create table UserSession (
 SessionKey text primary key,
 UserID int not null, -- Could have a hard "references User"
 LoginTime <time type> not null,
 LastSeenTime <time type> not null
)
当用户在 TLS 下通过 POST 登录您的站点时,请确定密码是否有效。 然后发出一个随机会话密钥,例如 50 个或更多加密随机字符以及安全 Cookie 中的内容。 将该会话密钥添加到 UserSession 表中。 然后,当您再次看到该用户时,首先访问 UserSession 表以查看 SessionKey 是否在其中,并且具有有效的 LoginTime 和 LastSeenTime 并且用户未被删除。您可以将其设计为计时器自动清除 UserSession 中的旧行。”

【讨论】:

我们倾向于喜欢 SO 的独立站点,所以您介意在此处也发布解决方案吗?以防万一链接在适当的时候发生变化(链接失效和其他什么......)未来的访问者可能会对此感到高兴。 这是一个公平的问题,恭敬地说。谢谢你。我已经包含了解决方案;你认为作者的名字也应该包括在内吗? (这是公开的,但我想知道这两种选择的礼仪。) 我觉得还是不错的。你不声称自己是这个 sn-p 的“所有者”,我看不出这个 sn-p 的原作者要求每个副本都需要一个归属。 (只有我的两分钱)。 您的数据库中不应该有“PasswordSalt”字段,因为您应该使用 bcrypt 作为您的哈希算法,它会自动创建一个盐并将其包含在返回的哈希中。还要使用常数时间比较函数。 +1 用于 bcrypt。此外,带有“加密”和“身份验证”密钥的 gorilla 会话将允许您在不使用数据库表的情况下安全地存储会话信息。【参考方案7】:

您将使用中间件进行身份验证。

您可以尝试go-http-auth 进行基本和摘要式身份验证,并尝试gomniauth 进行 OAuth2。

但如何进行身份验证实际上取决于您的应用。

身份验证将状态/上下文引入您的 http.Handlers,最近对此进行了一些讨论。

上下文问题的众所周知的解决方案是gorilla/context 和google context 描述的here。

我在go-on/wrap 中做了一个更通用的解决方案,不需要全局状态,它可以一起使用,也可以不使用其他两个,并且很好地与上下文无关中间件集成。

wraphttpauth 提供 go-http-auth 与 go-on/wrap 的集成。

【讨论】:

初学者有很多新东西。我想知道初学者应该从什么样的事情开始。 go-http-authgomniauth 或两者兼而有之? 这里有人在 golang 中实现了 OAuth 1.0 吗?基于 ConsumerKey 和 Secret 的身份验证? 如何实现 oAuth 1.0?使用消费者密钥和秘密?请帮忙。我没有得到任何相同的图书馆。

以上是关于人们如何在 Go 中管理身份验证? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如何在 python 中以管理员身份进行身份验证、查询和变异 Hasura? [关闭]

如何进行多个身份验证:laravel 中的用户、管理员、卖家 [关闭]

使用提供程序在 Flutter 中进行 Firebase 电话身份验证 [关闭]

Web 应用程序的 RESTful 身份验证 [关闭]

会话管理:如何为 REST 服务生成身份验证令牌? (球衣)

如何在不关闭当前 Firebase 会话的情况下创建用户身份验证 [重复]