对于那些在Go中构建RESTful API和JS前端应用程序的人,您如何管理身份验证?您使用的是任何特定的库或技术吗?
我很惊讶地发现这个问题的讨论很少。我记得下面的答案,并试图避免开发自己的实现:
Authentication Form in ASP.Net
每个人都分别编写自己的解决方案吗?
答案 0 :(得分:106)
这个问题得到了很多观点 - 并且有一个热门问题徽章 - 所以我知道这个话题有很多潜在的兴趣,很多人都在问同样的事情而没有在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
)
答案 1 :(得分:16)
答案 2 :(得分:14)
您可以使用中间件进行身份验证。
您可以尝试使用go-http-auth进行基本和摘要式身份验证,并使用gomniauth进行OAuth2。
但如何进行身份验证实际上取决于您的应用。
身份验证将状态/上下文引入您的http.Handler,最近有一些讨论。
上下文问题的众所周知的解决方案是gorilla/context和google context描述here。
我提出了一个更通用的解决方案,而不需要go-on/wrap中可以一起使用或没有其他两个的全局状态,并且可以很好地与无上下文的中间件集成。
wraphttpauth提供了go-http-auth与go-on / wrap的集成。
答案 3 :(得分:6)
另一个用于处理Cookie身份验证的开源软件包是httpauth。
(顺便说一句,由我写的)
答案 4 :(得分:5)
在2018年回答这个问题。我建议使用JWT(JSON Web Token)。您标记为已解决的答案有缺点,即前面(用户)和后面(服务器/数据库)的行程。更糟糕的是,如果用户经常请求需要auth,将导致来自/到服务器和数据库的膨胀请求。为了解决这个问题,使用JWT将令牌存储在用户端,用户可以在需要访问/请求时随时使用。无需访问数据库和服务器处理以在短时间内检查令牌有效性。
答案 5 :(得分:4)
老实说,您可以将许多身份验证方法和技术安装到应用程序中,这取决于应用程序的业务逻辑和要求。
例如Oauth2,LDAP,本地身份验证等。
我的答案假设您正在寻找本地身份验证,这意味着您可以在应用程序中管理用户的身份。
服务器必须公开一组允许用户和管理员使用的外部API
管理帐户以及他们如何向Server标识自己的身份以实现可信任的通信。
您最终将创建一个包含用户信息的数据库表。
出于安全目的对密码进行哈希加密的位置,请参见How to store the password in the database
基于以下方法之一,假设应用程序要求对用户进行身份验证:
基本身份验证(用户名,密码):
此auth方法取决于在base64中编码并在rfc7617中定义的Authorization标头中的用户凭据集,基本上,当应用程序收到用户请求时,它会解码其授权并重新哈希密码以在数据库哈希中进行比较(如果匹配)用户通过身份验证,否则将向用户返回401状态代码。
基于证书的身份验证:
此身份验证方法取决于数字证书来识别用户,
并且称为x509身份验证,因此当应用程序接收到用户请求时,它将读取客户端的证书并验证其是否与提供给该应用程序的CA Root证书匹配。
承载令牌:
此auth方法取决于短期访问令牌。承载令牌是一个神秘的字符串,通常由服务器根据登录请求生成。因此,当应用收到用户请求时,它会读取授权并验证令牌以验证用户身份。
但是,我建议使用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!!
您可以一次启用多种身份验证方法。通常应至少使用两种方法
答案 6 :(得分:0)
查看Labstack Echo - 它将RESTful API和前端应用程序的身份验证包装到可用于保护特定API路由的中间件中。
例如,设置基本身份验证与为def save_stuff(f, list_of_arrays):
list_of_lists = [list(arr) for arr in list_of_arrays]
json.dump(f, list_of_lists)
def load_stuff(f):
list_of_lists = json.load(f)
list_of_arrays = [np.array(lst) for lst in list_of_lists]
return list_of_arrays
路由创建新的子路由一样简单:
/admin
See all of Labstack's middleware authentication options here.