对于那些在Go中构建RESTful api和JS前端应用程序的人,你们是如何管理身份验证的?您是否使用了特定的库或技术?
我惊讶地发现关于这方面的讨论如此之少。我一直牢记以下答案,并试图避免开发自己的实现:
ASP中的认证表单。网
每个人都单独编写自己的解决方案吗?
对于那些在Go中构建RESTful api和JS前端应用程序的人,你们是如何管理身份验证的?您是否使用了特定的库或技术?
我惊讶地发现关于这方面的讨论如此之少。我一直牢记以下答案,并试图避免开发自己的实现:
ASP中的认证表单。网
每个人都单独编写自己的解决方案吗?
当前回答
看看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
}))
点击这里查看Labstack的所有中间件身份验证选项。
其他回答
2018年回答这个问题。我建议使用JWT(JSON Web Token)。你标记解决的答案有缺点,这是它做了前(用户)和后(服务器/db)的旅行。更糟糕的是,如果用户频繁请求需要认证,将导致从/到服务器和数据库的请求膨胀。为了解决这个问题,使用JWT将令牌存储在用户端,用户可以在任何需要访问/请求的时候使用它。不需要访问数据库和服务器处理来检查令牌有效性,只需很短的时间。
另一个可能的解决方案是Authboss,最近在邮件列表中宣布。
(我还没有试过使用这个库。)
另见最好的方法使一个web应用程序与用户认证?
另一个使用cookie处理身份验证的开源包是httpauth。
(顺便说一下,是我写的)
这个问题获得了大量的评论——并且有一个热门问题徽章——所以我知道这个话题有很多潜在的兴趣,很多人都在问同样的问题,而不是在互联网上找到答案。
大多数可用信息的结果都是类似于挥手的文本,作为“读者的练习”。;)
然而,我终于找到了一个具体的例子,(慷慨地)由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中的旧行。”
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!!
可以同时启用多个认证方法。通常至少应该使用两种方法