跟Gin一塊搭建自己的web框架(七)

本篇介紹HTTP Basic Auth的實現以及Recovery機制。

HTTP Basic Auth

Basic Auth是一種開放平臺認證方式,簡單的說就是需要你輸入用戶名和密碼才能繼續訪問。對於Basic Auth的概念不過多的進行介紹,直接進入如何實現的過程。

Basic Auth說白了就是賬號和密碼的組合,所以定義用來存儲賬號和密碼的結構體。

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

其中, Accounts 用來存放原始原始的賬號密碼組合, Pairs 用來存經過編碼的賬號密碼組合。

Basic Auth初始化的過程,就是將 “賬號:密碼” 串經過base64編碼之後,存放在Pairs中。

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
}

在訪問對應url的時候,會提取auth字段,然後將auth字段在我們保存的Pairs 中進行比對查找,如果找到了,就會返回對應的user

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

相應的, 如果我們需要對一個 url 開啓Basic Auth認證,首先定義一個路由組,調用對應的Basic Auth的中間件函數。

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

然後把需要認證的url都掛在下邊

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

這樣當我們訪問 /auth/secret 的時候,就需要經過Basic Auth的認證。如果我們在瀏覽器端訪問,會彈出一個提示框,要求輸入賬號和密碼; 如果是在 postman 等工具中訪問, 需要在header中添加一個 Authorization 字段,字段的值是 “Basic 賬號:密碼的base64編碼”。 比如賬號是admin, 密碼是password, 那麼“admin:password” 經過base64編碼的結果是“YWRtaW46cGFzc3dvcmQ=”, 所以對應的Authorization填的值應該是 “Basic YWRtaW46cGFzc3dvcmQ=”。

Recovery

Recovery在web框架中也是一個必不可少的中間件,如果某個請求出現了panic,服務需要捕獲請求中的panic信息,並且把信息打印到日誌中,有利於後期的維護和修復。

Recovery 其實就是定義了一個defer函數來處理請求中的panic,在defer中,會通過golang的recover() 函數catch住panic,然後將對應的棧信息打印出來。

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

		c.Next()
	}
}

一般來說,log中間件和recovery中間件都是必須的,所以可以定義一個默認的框架初始化函數,自動把log中間件和recovery中間件加載進去。

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

至此,一個簡易版本的gin web框架就成型了,包括最基本的日誌功能、錯誤恢復、路由功能、認證功能等。當然也可以在這個基礎之上在增加ORM等模塊,當是後續的功能擴展了。

完整的代碼參見: https://github.com/harleylau/myGin/tree/master/v0.5

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章