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

huangapple go评论71阅读模式
英文:

How are people managing authentication in Go?

问题

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

我很惊讶地发现关于这个问题的讨论如此少。我记住了像下面这样的答案,并且试图避免开发自己的实现:

https://stackoverflow.com/questions/20062103/authentication-in/20062137#20062137

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

英文:

For those building RESTful APIs and JS front-end apps in Go, how are you managing authentication? Are you using any particular libraries or techniques?

I'm surprised to find so little discussion about this. I keep in mind answers like the following, and am trying to avoid developing my own implementation:

https://stackoverflow.com/questions/20062103/authentication-in/20062137#20062137

Is everybody coding their own solution, separately?

答案1

得分: 123

这个问题得到了很多的浏览量,并且获得了一个热门问题徽章,所以我知道这个话题引起了很多潜在的兴趣,很多人都在问同样的问题,但在互联网上找不到答案。

大部分可用的信息都只是文字上的泛泛之谈,留给读者自己去思考。

然而,我终于找到了一个具体的例子,由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
)
  • 当用户通过TLS进行POST登录到你的网站时,确定密码是否有效。
  • 然后发放一个随机的会话密钥,比如50个或更多的加密随机字符,并将其存储在一个安全的Cookie中。
  • 将该会话密钥添加到UserSession表中。
  • 当你再次看到该用户时,首先查询UserSession表,看看SessionKey是否存在,并且具有有效的LoginTime和LastSeenTime,并且用户未被删除。你可以设计一个定时器来自动清除UserSession中的旧记录。”
英文:

This question gets a ton of views--and has a Popular Question badge--so I know there is a lot of latent interest in this topic, and many people are asking exactly the same thing and not finding answers on the Interwebs.

Most of the available information results in the textual equivalent of the hand wavy thing, left as an "exercise for the reader." 人们如何在Go中管理身份验证?

However I've finally located one concrete example, (generously) provided by a member of the golang-nuts mailing list:

https://groups.google.com/forum/#!msg/golang-nuts/GE7a_5C5kbA/fdSnH41pOPYJ

This provides a suggested schema and server-side implementation as a basis for custom authentication. The client-side code is still up to you.

(I hope the author of the post sees this: Thanks!)

Excerpted (and reformatted):


"I would suggest something like the following design:

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
)
  • When a user logs in to your site via a POST under TLS, determine if the password is valid.
  • Then issue a random session key, say 50 or more crypto rand characters and stuff in a secure Cookie.
  • Add that session key to the UserSession table.
  • Then when you see that user again, first hit the UserSession table to see if the SessionKey is in there with a valid LoginTime and LastSeenTime and User is not deleted. You could design it so a timer automatically clears out old rows in UserSession."

答案2

得分: 21

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

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

还可以参考Best way to make a webapp with user auth?

英文:

Another possible solution is Authboss, recently announced on the mailing list.

(I haven't tried using this library.)

Also see Best way to make a webapp with user auth?

1: https://github.com/go-authboss/authboss "Authboss"
2: https://groups.google.com/forum/#!topic/golang-nuts/1AbkiW1TUEc
3: https://www.reddit.com/r/golang/comments/2rhdxo/best_way_to_make_a_webapp_with_user_auth/

答案3

得分: 14

你可以使用中间件来进行身份验证。

对于基本认证和摘要认证,你可以尝试使用go-http-auth,对于OAuth2,你可以尝试使用gomniauth

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

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

解决上下文问题的一些著名解决方案包括gorilla/contextgoogle context,可以在这里找到相关描述。

我开发了一个更通用的解决方案,不需要全局状态,名为go-on/wrap,它可以与上述两个解决方案一起使用,也可以单独使用,并且与无上下文的中间件很好地集成。

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

英文:

You would use middleware to do the authentication.

You can try go-http-auth for basic and digest authentication and gomniauth for OAuth2.

But how to authenticate really depends on your app.

Authentication introduces state/context into your http.Handlers and there have been some discussion about that lately.

Well known solutions to the context problem are gorilla/context and google context described here.

I made a more general solution without the need of global state in go-on/wrap that may be used together or without the other two and nicely integrates with context free middleware.

wraphttpauth provides integration of go-http-auth with go-on/wrap.

答案4

得分: 13

我建议在2018年使用JWT(JSON Web Token)来解决这个问题。你标记为解决的答案有一个缺点,就是它在前端(用户)和后端(服务器/数据库)之间来回传递。更糟糕的是,如果用户频繁发送需要身份验证的请求,将导致服务器和数据库之间的请求变得臃肿。为了解决这个问题,可以使用JWT将令牌存储在用户端,用户可以在需要访问/请求时随时使用。不需要向数据库和服务器发送请求来检查令牌的有效性,这样可以节省时间。

英文:

Answering this in 2018. I suggest using JWT(JSON Web Token). The answer you marked solved has drawback, which is the trip it did front(user) and back(server/db). What is worse if user did frequent request that need auth, will result in bloated request from/to server and database. To solve this use JWT which store the token in user end which can be used by user anytime it needs access/request. No need trip to database and server processing to check the token validity take short time.

答案5

得分: 10

老实说,有很多身份验证方法和技术可以应用到你的应用程序中,这取决于应用程序的业务逻辑和需求。

例如,OAuth2、LDAP、本地身份验证等。我的回答假设你正在寻找本地身份验证,这意味着你在应用程序中管理用户的身份。

服务器必须公开一组外部 API,允许用户和管理员管理账户,并确定他们希望如何向服务器进行身份验证以实现可信通信。

你将需要创建一个数据库表来存储用户的信息,其中密码会进行哈希处理以提高安全性。参考如何在数据库中存储密码

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

  • 基本身份验证(用户名、密码):
    这种身份验证方法依赖于在授权头中以Base64编码定义的用户凭据集。当应用程序接收到用户请求时,它会解码授权信息,并重新对密码进行哈希处理,然后与数据库中的哈希进行比较,如果匹配则用户通过身份验证,否则返回401状态码给用户。

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

  • Bearer Token:
    这种身份验证方法依赖于短期的访问令牌。Bearer Token是一个加密的字符串,通常由服务器在登录请求的响应中生成。当应用程序接收到用户请求时,它会读取授权信息并验证令牌以对用户进行身份验证。

然而,我推荐使用go-guardian作为身份验证库,它通过一组可扩展的身份验证方法(称为策略)来实现。Go-Guardian不会挂载路由或假设任何特定的数据库模式,这最大程度地提高了灵活性,并允许开发人员做出决策。

设置go-guardian身份验证器非常简单。以下是上述方法的完整示例:

// 代码示例

使用方法:

  • 获取令牌:
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!!

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

英文:

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.<br>
For example Oauth2, LDAP, local authentication, etc. <br>
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

let assume app requirements to authenticate users based on one of the following methods:<br>

  • basic authentication (username, password): <br>
    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:<br>
    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: <br>
    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.

However, I'd recommend go-guardian
for authentication library which it does through an extensible set of authentication methods known as strategies. basically Go-Guardian does not mount routes or assume any particular database schema, which maximizes flexibility and allows decisions to be made by the developer.

Setting up a go-guardian authenticator is straightforward.

Here the full example of the above methods.

package main

import (
	&quot;context&quot;
	&quot;crypto/x509&quot;
	&quot;encoding/pem&quot;
	&quot;fmt&quot;
	&quot;io/ioutil&quot;
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;sync&quot;

	&quot;github.com/golang/groupcache/lru&quot;
	&quot;github.com/gorilla/mux&quot;
	&quot;github.com/shaj13/go-guardian/auth&quot;
	&quot;github.com/shaj13/go-guardian/auth/strategies/basic&quot;
	&quot;github.com/shaj13/go-guardian/auth/strategies/bearer&quot;
	gx509 &quot;github.com/shaj13/go-guardian/auth/strategies/x509&quot;
	&quot;github.com/shaj13/go-guardian/store&quot;
)

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(&quot;Executing Auth Middleware&quot;)
		user, err := authenticator.Authenticate(r)
		if err != nil {
			code := http.StatusUnauthorized
			http.Error(w, http.StatusText(code), code)
			return
		}
		log.Printf(&quot;User %s Authenticated\n&quot;, user.UserName())
		next.ServeHTTP(w, r)
	})
}

func Resource(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(&quot;Resource!!\n&quot;))
}

func Login(w http.ResponseWriter, r *http.Request) {
	token := &quot;90d64460d14870c08c81352a05dedd3465940a7&quot;
	user := auth.NewDefaultUser(&quot;admin&quot;, &quot;1&quot;, nil, nil)
	cache.Store(token, user, r)
	body := fmt.Sprintf(&quot;token: %s \n&quot;, 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(&quot;&lt;root-ca&gt;&quot;))

	cache = &amp;store.LRU{
		lru.New(100),
		&amp;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(&quot;/resource&quot;, middleware(http.HandlerFunc(Resource)))
	r.HandleFunc(&quot;/login&quot;, middleware(http.HandlerFunc(Login)))

	log.Fatal(http.ListenAndServeTLS(&quot;:8080&quot;, &quot;&lt;server-cert&gt;&quot;, &quot;&lt;server-key&gt;&quot;, 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 == &quot;stackoverflow&quot; &amp;&amp; password == &quot;stackoverflow&quot; {
		return auth.NewDefaultUser(&quot;stackoverflow&quot;, &quot;10&quot;, nil, nil), nil
	}

	return nil, fmt.Errorf(&quot;Invalid credentials&quot;)
}

func readCertificate(file string) *x509.Certificate {
	data, err := ioutil.ReadFile(file)

	if err != nil {
		log.Fatalf(&quot;error reading %s: %v&quot;, file, err)
	}

	p, _ := pem.Decode(data)
	cert, err := x509.ParseCertificate(p.Bytes)
	if err != nil {
		log.Fatalf(&quot;error parseing certificate %s: %v&quot;, file, err)
	}

	return cert
}

Usage:

  • Obtain token:
curl  -k https://127.0.0.1:8080/login -u stackoverflow:stackoverflow
token: 90d64460d14870c08c81352a05dedd3465940a7

  • Authenticate with a token:
curl  -k https://127.0.0.1:8080/resource -H &quot;Authorization: Bearer 90d64460d14870c08c81352a05dedd3465940a7&quot;

Resource!!
  • Authenticate with a user credential:
curl  -k https://127.0.0.1:8080/resource -u stackoverflow:stackoverflow

Resource!!
  • Authenticate with a user certificate:
curl --cert client.pem --key client-key.pem --cacert ca.pem https://127.0.0.1:8080/resource

Resource!!

You can enable multiple authentication methods at once. You should usually use at least two methods

答案6

得分: 6

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

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

英文:

Another open source package for handling authentication with cookies is httpauth.

(written by me, by the way)

答案7

得分: 1

请看一下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的所有中间件身份验证选项。

英文:

Take a look at Labstack Echo - it wraps authentication for RESTful APIs and frontend applications into middleware that you can use to protect specific API routes.

Setting up basic authentication, for example, is as straightforward as creating a new subrouter for the /admin route:

e.Group(&quot;/admin&quot;).Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
if username == &quot;joe&quot; &amp;&amp; password == &quot;secret&quot; {
return true, nil
}
return false, nil
}))

See all of Labstack's middleware authentication options here.

huangapple
  • 本文由 发表于 2014年8月9日 21:09:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/25218903.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定