介紹
每當(dāng)我們打開一個網(wǎng)址的時候,會自動彈出一個認(rèn)證界面,要求我們輸入用戶名和密碼,這種BasicAuth是最基礎(chǔ)、最常見的認(rèn)證方式,gin框架中提供了一種內(nèi)置的方式,但它只能用內(nèi)置的用戶和密碼,無法使用外部db中的用戶和密碼,這種方式很多時候是不友好的。
為此,本文根據(jù)gin.BasicAuth的原理對其就行重構(gòu),實(shí)現(xiàn)一個簡單的newAuth中間件,該中間件可以代替默認(rèn)的BasicAuth,并且可以按需更改為自定義查詢函數(shù),實(shí)現(xiàn)從外部db或者用戶管理系統(tǒng)查詢信息實(shí)現(xiàn)登錄認(rèn)證的功能。
gin BasicAuth 解析
博文 gin框架14–使用 BasicAuth 中間件 介紹了BasicAuth 中間件的基礎(chǔ)使用方法,直接使用 gin.BasicAuth(gin.Accounts{“foo”: “bar”, “austin”: “1234”, “l(fā)ena”: “hello2”, “manu”: “4321”, }) 即可,非常簡單實(shí)用。
實(shí)際上當(dāng)我們訪問url的時候,它會從請求的 Authorization 中獲取用戶信息,并和gin.Accounts中內(nèi)置用戶對比,如果用戶存在就將用戶名稱存放在Context的 Keys map結(jié)構(gòu)中,方便后續(xù)查找或者獲取用戶信息;如果不存在就設(shè)置c.Header(“WWW-Authenticate”, realm), 并返回c.AbortWithStatus(http.StatusUnauthorized),瀏覽器上的表現(xiàn)就是重新彈出輸入用戶名和密碼的窗口 。
核心邏輯在 BasicAuthForRealm 方法中,如下所示:
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
if realm == "" {
realm = "Authorization Required"
}
realm = "Basic realm=" + strconv.Quote(realm)
pairs := processAccounts(accounts)
return func(c *Context) {
// Search user in the slice of allowed credentials
user, found := pairs.searchCredential(c.requestHeader("Authorization"))
if !found {
// Credentials doesn't match, we return 401 and abort handlers chain.
c.Header("WWW-Authenticate", realm)
c.AbortWithStatus(http.StatusUnauthorized)
return
}
// The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using
// c.MustGet(gin.AuthUserKey).
c.Set(AuthUserKey, user)
}
}
自定義newAuth實(shí)現(xiàn)基礎(chǔ)認(rèn)證
gin.BasicAuth 只能提供默認(rèn)的認(rèn)證功能,且需要內(nèi)置指定的用戶|密碼,但實(shí)際在代碼中hardcode大量用戶信息是不科學(xué)的,因此我們需要自己重構(gòu)一個BasicAuth來實(shí)驗(yàn)基礎(chǔ)認(rèn)證功能。
此處實(shí)現(xiàn)了一個newAuth中間件,該中間件會判斷用戶是否輸入賬號|密碼,并通過judgeUserExist來判斷賬號|密碼是否正確,正確則返回用戶信息,不正確則返回http.StatusUnauthorized, 具體案例如下。
此處為了簡潔方便,此處直接內(nèi)置了3個用戶到users中,并用 judgeUserExist 查詢用戶賬號密碼是否正確。實(shí)際項(xiàng)目中可將該方法更改為查詢db,無需在項(xiàng)目中hardcode內(nèi)置用戶。文章來源:http://www.zghlxwxcb.cn/news/detail-721457.html
package main
import (
"encoding/base64"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
"strings"
)
var users = gin.H{
"foo": gin.H{"email": "foo@bar.com", "phone": "123433", "pwd": "bar"},
"austin": gin.H{"email": "austin@example.com", "phone": "666", "pwd": "123"},
"lena": gin.H{"email": "lena@guapa.com", "phone": "523443", "pwd": "456"},
}
func help() string {
helpStr := `hello gin:
127.0.0.1:8088/your-api
/auth/user
`
return helpStr
}
func judgeUserExist(userName, userPwd string) (string, bool) {
// 實(shí)際項(xiàng)目中將該函數(shù)更改為從db查詢即可,此處為了簡單直接從預(yù)定的users中查詢。
msg := ""
tag := false
if userInfo, ok := users[userName]; ok {
pwd, ok := userInfo.(gin.H)["pwd"]
if ok && pwd == userPwd {
msg = fmt.Sprintf("用戶%v密碼正確", userName)
tag = true
} else {
msg = fmt.Sprintf("用戶%v密碼不正確", userName)
}
} else {
msg = fmt.Sprintf("用戶%v不存在", userName)
}
return msg, tag
}
func getUserPwdFromAuthorization(auth string) (user, pwd string) {
// auth[:6]="Basic "
base64UserPwd, err := base64.StdEncoding.DecodeString(auth[6:])
if err != nil {
panic(err)
}
base64UserPwdStr := string(base64UserPwd)
colonIndex := strings.Index(base64UserPwdStr, ":")
user = base64UserPwdStr[:colonIndex]
pwd = base64UserPwdStr[colonIndex+1:]
return user, pwd
}
func newAuth(realm string) func(c *gin.Context) {
if realm == "" {
realm = "Authorization Required"
}
realm = "Basic realm=" + strconv.Quote(realm)
return func(c *gin.Context) {
authHeader := c.Request.Header.Get("Authorization") // 獲取請求頭中的數(shù)據(jù)
if authHeader == "" {
c.Header("WWW-Authenticate", realm)
c.AbortWithStatus(http.StatusUnauthorized)
return
} else {
user, pwd := getUserPwdFromAuthorization(authHeader)
// fmt.Printf("user=%v,pwd=%v\n", user, pwd)
msg, tag := judgeUserExist(user, pwd)
if !tag {
// c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"msg": msg, "tag": tag})
fmt.Println(msg)
c.AbortWithStatus(http.StatusUnauthorized)
return
}
c.Set(gin.AuthUserKey, user)
}
}
}
func userHandler(c *gin.Context) {
user := c.MustGet(gin.AuthUserKey).(string)
c.IndentedJSON(http.StatusOK, gin.H{
"status": 200,
"msg": "it's fine",
"userInfo": users[user],
})
}
func main() {
app := gin.Default()
app.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, help())
})
authorized := app.Group("/auth", newAuth(""))
authorized.GET("/user", userHandler)
app.Run(":8088")
}
輸出:文章來源地址http://www.zghlxwxcb.cn/news/detail-721457.html
注意事項(xiàng)
- c.Header中需要添加 WWW-Authenticate 字段,否則初次訪問的時候不會彈出輸入用戶名、密碼的框!!!
說明
- 測試環(huán)境
ubuntu22.04 Desktop
go1.20.7 - 參考文檔
using-basicauth-middleware
Gin框架 -- 中間件
到了這里,關(guān)于gin框架39--重構(gòu) BasicAuth 中間件的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!