人们如何在Go中管理身份验证?

Sex*_*hor 180 authentication go

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

我很惊讶地发现这个问题很少.我记得下面的答案,我试图避免开发自己的实现:

ASP.Net中的身份验证表单

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

Sex*_*hor 112

这个问题得到了很多观点 - 并且有一个热门问题徽章 - 所以我知道这个话题有很多潜在的兴趣,很多人都在问同样的事情,而不是在Interwebs上找到答案.

大多数可用信息的结果都是手工波浪形的文本等同物,留作"读者练习".;)

然而,我终于找到了一个具体的例子,(慷慨地)由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
)
Run Code Online (Sandbox Code Playgroud)
  • 当用户通过TLS下的POST登录到您的站点时,请确定密码是否有效.
  • 然后发出一个随机会话密钥,比如50个或更多加密rand字符和安全Cookie中的东西.
  • 将该会话密钥添加到UserSession表.
  • 然后,当您再次看到该用户时,首先点击UserSession表以查看SessionKey是否在那里,并且具有有效的LoginTime和LastSeenTime,并且不删除用户.您可以设计它,以便计时器自动清除UserSession中的旧行."

  • 数据库中不应该有"PasswordSalt"字段,因为您应该使用bcrypt作为哈希算法,该算法会自动创建一个salt并将其包含在返回的哈希中.还使用恒定时间比较功能. (32认同)
  • 我们倾向于在S​​O处寻找一个自包含的网站,所以您是否也想在这里发布解决方案?以防链接在适当的时候发生变化(链接腐烂以及其他什么......)未来的访问者可能会对此感到高兴. (8认同)
  • 为bcrypt +1.此外,使用"加密"和"身份验证"密钥的大猩猩会话将允许您安全地存储会话信息,而无需使用数据库表. (4认同)

Sex*_*hor 17

另一种可能的解决方案是最近在邮件列表上公布的Authboss.

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

另请参阅使用用户身份验证制作webapp的最佳方法?

  • 我赞成这一点,因为搜索"golang身份验证"的人可能不会寻找通用的"你将如何解决这个问题",他们正在寻找一个现成的,经过测试的解决方案.感谢发布此内容. (7认同)

met*_*ule 14

您可以使用中间件进行身份验证.

您可以尝试使用go-http-auth进行基本和摘要式身份验证以及OAuth2的gomniauth.

但是如何进行身份验证确实取决于您的应用.

身份验证将状态/上下文引入到http.Handler中,最近有一些讨论.

上下文问题的众所周知的解决方案是这里描述的大猩猩/上下文谷歌上下文.

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

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


mfa*_*has 9

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


小智 8

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

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

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

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

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

但是,我建议使用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.ExtKeyUsage{x509.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 == "stackoverflow" && password == "stackoverflow" {
        return auth.NewDefaultUser("stackoverflow", "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
}

Run Code Online (Sandbox Code Playgroud)

用法:

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

Run Code Online (Sandbox Code Playgroud)
  • 使用令牌进行身份验证:
curl  -k https://127.0.0.1:8080/resource -H "Authorization: Bearer 90d64460d14870c08c81352a05dedd3465940a7"

Resource!!
Run Code Online (Sandbox Code Playgroud)
  • 使用用户凭据进行身份验证:
curl  -k https://127.0.0.1:8080/resource -u stackoverflow:stackoverflow

Resource!!
Run Code Online (Sandbox Code Playgroud)
  • 使用用户证书进行身份验证:
curl --cert client.pem --key client-key.pem --cacert ca.pem https://127.0.0.1:8080/resource

Resource!!
Run Code Online (Sandbox Code Playgroud)

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


Cam*_*tle 6

用于处理cookie身份验证的另一个开源软件包是httpauth.

(顺便说一句,由我写的)