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

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

ASP中的认证表单。网

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


当前回答

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

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

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

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中的旧行。”

其他回答

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

Honestly, there's a lot of authentication methods and techniques that you can mount into your application and that depends on applications business logic and requirements. For example Oauth2, LDAP, local authentication, etc. My answer assumes you are looking for local authentication which means you manage the user's identities in your application. The server must expose a set of external API allow users and admins Managing the accounts and how they want to identify themselves to Server to achieve trustable communication. you will end up creating a DB table holding the user's information. where the password is hashed for security purposes See How to store the password in the database

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

basic authentication (username, password): This auth method depends on user credentials sets in Authorization header encoded in base64 and defined inrfc7617, basically when the app receives the user requests its decodes the authorization and re-hash the password to compare it within DB hash if it's matched the user authenticated otherwise return 401 status code to the user. certificate-based authentication: This auth method depends on a Digital Certificate to identify a user, and it's known as x509 auth, so when the app receives the user requests it reads the client's certificate and verifies it that matches the CA Root certificate that is provided to the APP. bearer token: This auth method depends on short-lived Access tokens, The bearer token is a cryptic string, usually generated by the server in response to a login request. so when the app receives the user requests it reads the authorization and validates the token to authenticate the user.

然而,我还是建议你去“监护人” 用于身份验证库,它通过一组称为策略的可扩展身份验证方法来实现。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!!

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

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

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

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

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

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

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

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

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

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

上下文问题的著名解决方案是这里描述的gorilla/context和谷歌context。

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

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