This article introduces the implementation and Recovery mechanism of HTTP Basic Auth.

HTTP Basic Auth

Basic Auth is an open platform authentication method that simply requires you to enter a user name and password to continue access. The concept of Basic Auth is introduced, and the process of how to implement it is directly entered.

Basic Auth is simply a combination of an account and a password, so define a structure for storing the account and password.

type( // BasicAuthPair . BasicAuthPair struct { Code string User string } // Account . Account struct { User string Password  string } // Accounts . Accounts []Account // Pairs . Pairs []BasicAuthPair )Copy the code

Accounts is used to store the original original account password combination and Pairs is used to store the encoded account password combination.

Basic Auth is initialized by storing the “Account: Password” string in Pairs after base64 encoding.

func processCredentials(accounts Accounts) (Pairs, error) {
	if len(accounts) == 0 {
		return nil, errors.New("Empty list of authorized credentials.")
	}
	pairs := make(Pairs, 0, len(accounts))
	for _, account := range accounts {
		if len(account.User) == 0 || len(account.Password) == 0 {
			return nil, errors.New("User or password is empty")
		}
		base := account.User + ":" + account.Password
		code := "Basic " + base64.StdEncoding.EncodeToString([]byte(base))
		pairs = append(pairs, BasicAuthPair{code, account.User})
	}
	// We have to sort the credentials in order to use bsearch later.
	sort.Sort(pairs)
	return pairs, nil
}
Copy the code

When accessing the corresponding URL, the Auth field will be extracted, and then the Auth field will be compared and searched in the Pairs we save. If it is found, the corresponding user will be returned

func searchCredential(pairs Pairs, auth string) string {
	if len(auth) == 0 {
		return ""
	}
	// Search user in the slice of allowed credentials
	r := sort.Search(len(pairs), func(i int) bool { return pairs[i].Code >= auth })

	if r < len(pairs) && subtle.ConstantTimeCompare([]byte(pairs[r].Code), []byte(auth)) == 1 {
		// user is allowed, set UserId to key "user" in this context, the userId can be read later using
		// c.Get("user"
		return pairs[r].User
	}
	return ""
}
Copy the code

Accordingly, if we need to enable Basic Auth authentication for a URL, we first define a routing group and call the corresponding Basic Auth middleware function.

accounts := gin.Accounts{
		{User: "admin", Password: "password"},
		{User: "foo", Password: "bar"},
	}
authorized := r.Group("/auth", gin.BasicAuth(accounts))
Copy the code

Then hang all the urls that need to be authenticated below

authorized.GET("/secret", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"secret": "The secret url need to be authorized",})})Copy the code

So when we access /auth/secret, we need to be authenticated by Basic Auth. If we access from the browser, a prompt box will pop up, asking for the account and password; If the access is performed using tools such as Postman, you need to add an Authorization field in the header. The value of the field is Basic Account: Base64 Encoding of the password. For example, if the user name is admin and the password is password, the base64 encoded result of admin:password is YWRtaW46cGFzc3dvcmQ=. Therefore, the corresponding Authorization value should be Basic YWRtaW46cGFzc3dvcmQ=.

Recovery

Recovery is also an essential middleware in the Web framework. If panic occurs in a request, the service needs to capture the panic information in the request and print the information in the log, which is conducive to later maintenance and repair.

Recovery actually defined a defer function to deal with the panic in the request. In defer, the panic was caught by Golang’s recover() function and the corresponding stack information was printed out.

func Recovery() HandlerFunc {
	return func(c *Context) {
		defer func() {
			if len(c.Errors) > 0 {
				log.Println(c.Errors)
			}
			iferr := recover(); err ! = nil { stack := stack(3) log.Printf("PANIC: %s\n%s", err, stack)
				c.Writer.WriteHeader(http.StatusInternalServerError)
			}
		}()

		c.Next()
	}
}
Copy the code

In general, both log and recovery middleware are required, so you can define a default framework initialization function to automatically load log and recovery middleware into it.

// Default Returns a Engine instance with the Logger and Recovery already attached.
func Default() *Engine {
	engine := New()
	engine.Use(Recovery(), Logger())
	return engine
}
Copy the code

At this point, a simple version of the Gin Web framework is in place, including basic logging, error recovery, routing, authentication, and so on. Of course, you can also add ORM and other modules on this basis, when the subsequent function expansion.

For the complete code, see: github.com/harleylau/m…