game guardian_使用go Guardian在Golang中编写可扩展身份验证

game guardian

In building web and REST API application, it is very important to build a system users can trust and rely on.

在构建Web和REST API应用程序时,构建用户可以信任和依赖的系统非常重要。

Authentication is important because it enables organizations and apps to keep their networks secure by permitting only authenticated users to access its protected resources.

身份验证很重要,因为它允许组织和应用程序仅允许经过身份验证的用户访问其受保护的资源,从而确保其网络的安全。

In this tutorial, We’ll be discussing how to handle authentication running in cluster mode using Golang and Go-Guardian library.

在本教程中,我们将讨论如何使用Golang和Go-Guardian库处理在集群模式下运行的身份验证。

问题 (Problem)

Authentication is just one of the possible places that could cause scalability issue, As long as user info stored or cached on the server-side.

只要用户信息存储或缓存在服务器端,身份验证只是可能导致可伸缩性问题的可能位置之一。

running a replicated application in cluster modes like Kubernetes, docker swarm, or even behind a load balancer, doesn’t guarantee to assign a single server to a particular user.

在Kubernetes,docker swarm甚至在负载均衡器后面的集群模式下运行复制的应用程序,不能保证为特定用户分配单个服务器。

用例和解决方案 (Use Case And Solution)

assume we have app replicated twice(A, B) and running behind a load balancer, the user asks for token the load balancer route request to replication A where the token generated and cached in the app memory now the same user request a protected resource and the load balancer route request to replication B this will end up with an authorization error.

假设我们将应用复制了两次(A,B)并在负载平衡器后面运行,则用户向负载A请求向复制A的负载平衡器路由请求的令牌,在复制A中,生成并缓存在应用内存中的令牌现在同一用户请求受保护的资源,并且负载均衡器向复制B的路由请求,最终将导致授权错误。

let’s figure out how we can solve the above problem and scale the app without performance degradation and keeping in mind this kind of service must be stateless.

让我们弄清楚如何解决上述问题,并在不降低性能的情况下扩展应用程序,并牢记此类服务必须是无状态的。

Suggested Solutions :

建议的解决方案:

  • save token in db and re-cache it in the server.

    将令牌保存在db中,然后将其重新缓存在服务器中。
  • distributed cache

    分布式缓存
  • shared cache

    共享缓存
  • sticky session

    粘性会议

in all the above solution we still have a problem, so ask your self what will happen if DB or shared cache down, or the app itself failed ?

在上述所有解决方案中,我们仍然存在问题,因此请问问自己,如果数据库或共享缓存出现故障,或者应用程序本身失败了,将会发生什么?

the optimal solution for this kind of issue is to use a stateless token where it can be signed and verified again.in this tutorial, we will use JWT as defined in RFC 7519,cause it Internet widely and most of the readers have used it or heard about it.

此类问题的最佳解决方案是使用无状态令牌,在该令牌上可以再次对其进行签名和验证。在本教程中,我们将使用RFC 7519中定义的JWT,因为它广泛应用于Internet,并且大多数读者都在使用它,或者听说了。

守护者概述 (Go-Guardian Overview)

Go-Guardian is a golang library that provides a simple, clean, and idiomatic way to create powerful modern API and web authentication.

Go-Guardian是一个golang库,它提供了一种简单,干净且惯用的方式来创建功能强大的现代API和Web身份验证。

Go-Guardian sole purpose is to authenticate requests, which it does through an extensible set of authentication methods known as strategies. Go-Guardian does not mount routes or assume any particular database schema, which maximizes flexibility and allows decisions to be made by the developer. The API is simple: you provide go-guardian a request to authenticate, and go-guardian invoke strategies to authenticate end-user request. Strategies provide callbacks for controlling what occurs when authentication should succeeds or fails.

Go-Guardian的唯一目的是对请求进行身份验证,它通过一组称为策略的可扩展身份验证方法来完成。 Go-Guardian不会挂载路由或采用任何特定的数据库架构,从而最大程度地提高了灵活性并允许开发人员做出决策。 该API很简单:您向go-guardian提供身份验证请求,然后go-guardian调用策略对最终用户请求进行身份验证。 战略为控制认证时发生了什么的回调should是成功还是失败。

为什么要守护? (Why Go-Guardian?)

When building a modern application, you don’t want to implement authentication module from scratch; you want to focus on building awesome software. go-guardian is here to help with that.

在构建现代应用程序时,您不想从头开始实施身份验证模块。 您想专注于构建出色的软件。 go-guardian可以为您提供帮助。

Here are a few bullet point reasons you might like to try it out:

以下是您可能要尝试的一些要点:

  • provides simple, clean, and idiomatic API.

    提供简单,干净且惯用的API。
  • provides top trends and traditional authentication methods.

    提供了最新趋势和传统的身份验证方法。
  • provides a package to caches the authentication decisions, based on different mechanisms and algorithms.

    提供基于不同机制和算法的用于缓存身份验证决策的程序包。
  • provides two-factor authentication and one-time password as defined in RFC-4226 and RFC-6238

    提供RFC-4226和RFC-6238中定义的两因素身份验证和一次性密码

创建我们的项目 (Creating our project)

We are going to start by creating our project.

我们将从创建我们的项目开始。

mkdir scalable-auth && cd scalable-auth && go mod init scalable-auth && touch main.go

This will create a new directory called “scalable-auth” and and initialize the project with go.mod.

这将创建一个名为“ scalable-auth”的新目录,并使用go.mod初始化项目。

We also need to install gorilla mux, go-guardian, jwt-go.

我们还需要安装gorilla mux,go-guardian, jwt-go 。

go get github.com/gorilla/mux
go get github.com/shaj13/go-guardian
go get "github.com/dgrijalva/jwt-go"

我们的第一行代码 (Our first lines of code)

Before we write any code we need to write some mandatory code to make the program run.

在编写任何代码之前,我们需要编写一些强制性代码以使程序运行。

package main
import (
"log"
)
func main() {
log.Println("Auth !!")
}

创建我们的端点 (Creating our endpoints)

We are going to remove the line that prints out “Auth !!” and add the gorilla Mux package and initialize the router.

我们将删除打印“ Auth !!”的行 并添加大猩猩Mux包并初始化路由器。

package main
import (
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
}

Now we are going to establish the endpoints of our API, the way we will set this up is to create all of our endpoints in the main function, every endpoint needs a function to handle the request and we will define those below the main function.

现在,我们将建立API的端点,我们的设置方法是在main函数中创建所有端点,每个端点都需要一个函数来处理请求,并在main函数下定义它们。

package main
import (
"net/http"
"log"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/v1/auth/token", createToken).Methods("GET")
router.HandleFunc("/v1/book/{id}", getBookAuthor).Methods("GET")
log.Println("server started and listening on http://127.0.0.1:8080")
http.ListenAndServe("127.0.0.1:8080", router)
}

What I’ve done is to create two different routes, first, API to obtain a bearer token and the second one to access a protected resource that returns a book author by id.

我要做的是创建两个不同的路由,第一个是API,以获取承载令牌,第二个是访问受保护资源,该资源通过ID返回图书作者。

路线处理程序 (Route handlers)

Now we just need to define the functions that will handle the requests.

现在,我们只需要定义将处理请求的功能。

createToken()

createToken()

func createToken(w http.ResponseWriter, r *http.Request) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "auth-app",
"sub": "medium",
"aud": "any",
"exp": time.Now().Add(time.
})jwtToken, _:= token.SignedString([]byte("secret"))
w.Write([]byte(jwtToken))
}

getBookAuthor()

getBookAuthor()

func getBookAuthor(w http.ResponseWriter, r *http.Request) {    vars := mux.Vars(r)
id := vars["id"]
books := map[string]string{
"1449311601": "Ryan Boyd",
"148425094X": "Yvonne Wilson",
"1484220498": "Prabath Siriwarden",
}
body := fmt.Sprintf("Author: %s \n", books[id])
w.Write([]byte(body))
}

Your file should now look something like this:

您的文件现在应如下所示:

Now lets run sample requests to test our code !!

现在,让我们运行示例请求以测试我们的代码!

curl  -k http://127.0.0.1:8080/v1/book/1449311601 
Author: Ryan Boydcurl -k http://127.0.0.1:8080/v1/auth/tokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhbnkiLCJleHAiOjE1OTczNjE0NDYsImlzcyI6ImF1dGgtYXBwIiwic3ViIjoibWVkaXVtIn0.EepQzhuAS-lnljTZad3vAO2vRbgflB53aUCfCnlbku4

与Go-Guardian集成 (Integrate with Go-Guardian)

first let’s add the following line before the main function.

首先,让我们在main函数之前添加以下行。

var authenticator auth.Authenticator
var cache store.Cache

and create two functions to verify the user credential and token.

并创建两个函数来验证用户凭证和令牌。

func validateUser(ctx context.Context, r *http.Request, userName, password string) (auth.Info, error) {
if userName == "medium" && password == "medium" {
return auth.NewDefaultUser("medium", "1", nil, nil), nil
}
return nil, fmt.Errorf("Invalid credentials")
}func verifyToken(ctx context.Context, r *http.Request, tokenString string) (auth.Info, error) {token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte("secret"), nil
})if err != nil {
return nil, err
}if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {user := auth.NewDefaultUser(claims["medium"].(string), "", nil, nil)
return user, nil
}
return nil , fmt.Errorf("Invaled token")
}

now we need another function to setup Go-Guardian.

现在我们需要另一个功能来设置Go-Guardian。

func setupGoGuardian() {   
authenticator = auth.New()
cache = store.NewFIFO(context.Background(), time.Minute*5)
basicStrategy := basic.New(validateUser, cache)
tokenStrategy := bearer.New(verifyToken, cache)
authenticator.EnableStrategy(basic.StrategyKey, basicStrategy)
authenticator.EnableStrategy(bearer.CachedStrategyKey, tokenStrategy)
}

What I’ve done is to setup Go-Gurdian, by creating authenticator that takes request and dispatch it to strategies and return user information from the first strategy that successfully authenticates the request.besides, initialize a cache to caches the authentication decision to improve server performance.

我要做的是通过创建身份验证器来设置Go-Gurdian,该身份验证器接受请求并将其分派到策略,并从成功验证请求的第一个策略返回用户信息。此外,初始化缓存以缓存身份验证决定以改善服务器性能。

Before we continue, we need an HTTP middleware to intercept the request and authenticate users before it reaches the final route.

在继续之前,我们需要一个HTTP中间件来拦截请求并验证用户,然后再到达最终路由。

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)
})
}

finally, we wrap the createToken and getBookAuthor functions in our main with the middleware to authenticate the request.

最后,我们将main的createToken和getBookAuthor函数与中间件包装在一起,以对请求进行身份验证。

middleware(http.HandlerFunc(createToken))
middleware(http.HandlerFunc(getBookAuthor))

Don’t forget to invoke set up GoGuardian in the first of the main function.

不要忘记在主要功能的第一个调用设置GoGuardian。

setupGoGuardian()

Your file should now look something like this:

您的文件现在应如下所示:

测试我们的API (Testing our API)

first lets run app twice from two different shell.

首先让我们从两个不同的Shell中运行应用两次。

PORT=8080 go run main.goPORT=9090 go run main.go

Obtain a user access token from replica A (port 8080)

从副本A(端口8080)获取用户访问令牌

curl  -k http://127.0.0.1:8080/v1/auth/token -u medium:mediumeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhbnkiLCJleHAiOjE1OTczNjI4NjksImlzcyI6ImF1dGgtYXBwIiwic3ViIjoibWVkaXVtIn0.SlignTJE3YD9Ecl24ygoYRu_9tVucCLop4vXWKzaRTw

Getting a book author using user access token from replica B (port 9090)

使用用户访问令牌从副本B(端口9090)获取书籍作者

curl  -k http://127.0.0.1:8080/v1/book/1449311601 -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhbnkiLCJleHAiOjE1OTczNjI4NjksImlzcyI6ImF1dGgtYXBwIiwic3ViIjoibWVkaXVtIn0.SlignTJE3YD9Ecl24ygoYRu_9tVucCLop4vXWKzaRTw"Author: Ryan Boyd

非常感谢您的阅读! (Thank you so much for reading!)

I hope you found this article helpful, I really hope I helped at least one person get familiar with the basics of building Server Authentication in Golang Using Go-Guardian.

希望本文对您有所帮助,我真的希望我帮助至少一个人熟悉了使用Go-Guardian在Golang中构建服务器身份验证的基础知识。

More About Go-Guardian can be found in GitHub and GoDoc

可以在GitHubGoDoc中找到有关Go-Guardian的更多信息

翻译自: https://medium.com/@hajsanad/writing-scalable-authentication-in-golang-using-go-guardian-83691219a73a

game guardian

你可能感兴趣的:(golang,go)