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

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

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

ASP. aspc中的认证表单。净< / >

每个人都单独编写自己的解决方案吗?

84158 次浏览

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

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

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

身份验证将状态/上下文引入http。最近有一些关于这个问题的讨论。

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

我做了一个更通用的解决方案,不需要继续/包装中的全局状态,可以一起使用,也可以不使用其他两个,并与上下文自由中间件很好地集成。

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

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

大多数可用信息的结果都是类似于挥手的文本,作为“读者的练习”。;)

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

< a href = " https://groups.google.com/forum/ !味精/ golang-nuts GE7a_5C5kbA / fdSnH41pOPYJ”> https://groups.google.com/forum/ !味精/ 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, user未被删除。你可以设计一个定时器自动清除UserSession中的旧行。”

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

(顺便说一下,是我写的)

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

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

也可参见最好的方法使一个web应用程序与用户认证?

2018年回答这个问题。我建议使用JWT(JSON Web Token)。你标记解决的答案有缺点,这是它做了前(用户)和后(服务器/db)的旅行。更糟糕的是,如果用户频繁请求需要认证,将导致从/到服务器和数据库的请求膨胀。为了解决这个问题,使用JWT将令牌存储在用户端,用户可以在任何需要访问/请求的时候使用它。不需要访问数据库和服务器处理来检查令牌有效性,只需很短的时间。

看看Labstack回声——它将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
}))

查看Labstack的所有中间件认证选项

老实说,有很多身份验证方法和技术可以安装到你的应用程序中,这取决于应用程序的业务逻辑和需求 例如Oauth2、LDAP、本地认证等
我的回答假设您正在寻找本地身份验证,这意味着您在应用程序中管理用户的身份。 服务器必须公开一组允许用户和管理员的外部API 管理帐户以及它们希望如何向服务器标识自己,以实现可信任的通信。 您将创建一个包含用户信息的DB表。 为安全起见,密码被散列参见如何在数据库中存储密码

假设应用程序要求基于以下方法之一验证用户:

  • basic authentication (username, password):
    这个认证方法依赖于授权头中base64编码并在__abc0中定义的用户凭据集,基本上当应用程序接收到用户请求时,它会解码授权并重新哈希密码,如果它与经过身份验证的用户匹配,则在DB哈希中进行比较,否则返回401状态码给用户

  • <李> < p >基于证书的身份验证:< br > 这种认证方法依赖于数字证书来识别用户, 它被称为x509认证,所以当应用程序接收到用户请求时,它会读取客户端证书,并验证它是否与提供给应用程序的CA根证书匹配
  • 不记名token:
    这种认证方法依赖于短期的访问令牌,承载令牌是一个加密字符串,通常由服务器在响应登录请求时生成。所以当应用程序接收到用户请求时,它会读取授权并验证令牌来验证用户的身份

但是,我建议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
}


用法:

  • 获得令牌:
curl  -k https://127.0.0.1:8080/login -u stackoverflow:stackoverflow
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 stackoverflow:stackoverflow


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


Resource!!

可以同时启用多个认证方法。通常至少应该使用两种方法