前文分別介紹過了Resty和gin-jwt兩個包,Resty
是一個HTTP和REST客戶端,gin-jwt
是一個實現了JWT的Gin中介軟體。本文將使用這兩個包來實現一個簡單的使用者登入功能。
環境準備
實現登入功能之前要提前準備一個用於查詢使用者是否存在的服務。訪問服務http://127.0.0.1:18081/users?username=root
時返回使用者root的相關資訊
{
"total": 1,
"data": [
{
"id": 1,
"username": "root",
"password": "CGUx1FN++xS+4wNDFeN6DA==",
"nickname": "超級管理員",
"mobile": "13323232323"
}
]
}
返回結果中password
欄位AES加密後的結果。當引數username
傳入其他字串時返回null
{
"total": 0,
"data": null
}
好了準備工作到此結束,下面來看一下如何實現登入功能。
實現認證
首先實現呼叫查詢使用者服務的方法。
func FindUser(userName string) (user sysUser.SysUser, err error) {
client := resty.New().SetRetryCount(3)
resp, err := client.R().
SetQueryParams(map[string]string{
"username": userName,
}).
SetResult(&PagedUser{}).
Get("http://127.0.0.1:18081/users")
if err != nil {
log.Panicln("FindUser err: ", err.Error())
}
response := resp.Result().(*PagedUser)
if response.Total == 1 {
user = response.Data[0]
return
}
err = errors.New("使用者不存在")
return
}
這裡我們建立了一個Resty
客戶端,並設定了3次重試,依照服務的要求傳入username
引數,然後通過Total
值判斷使用者是否存在,使用者存在的話返回使用者資訊,否則返回錯誤。
接下來我們實現有關jwt驗證的部分。
var identityKey = "id"
type login struct {
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
type User struct {
Id int
UserName string
NickName string
}
func JwtMiddleware() (authMiddleware *jwt.GinJWTMiddleware, err error) {
authMiddleware, err = jwt.New(&jwt.GinJWTMiddleware{
Realm: "test zone",
Key: []byte("secret key"),
Timeout: time.Hour,
MaxRefresh: time.Hour,
IdentityKey: identityKey,
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(*User); ok {
return jwt.MapClaims{
identityKey: v.UserName,
}
}
return jwt.MapClaims{}
},
IdentityHandler: func(c *gin.Context) interface{} {
claims := jwt.ExtractClaims(c)
return &User{
UserName: claims[identityKey].(string),
}
},
Authenticator: func(c *gin.Context) (interface{}, error) {
var loginVals login
if err := c.ShouldBind(&loginVals); err != nil {
return "", jwt.ErrMissingLoginValues
}
userID := loginVals.Username
password := loginVals.Password
user, err := http_service.FindUser(userID)
if err != nil {
return nil, jwt.ErrFailedAuthentication
}
encrypt := utils.PasswordEncrypt(password, userID)
if encrypt != user.Password.String {
return nil, jwt.ErrFailedAuthentication
}
return &User{
Id: user.Id,
UserName: user.Username.String,
NickName: user.Nickname.String,
}, nil
},
Authorizator: func(data interface{}, c *gin.Context) bool {
if v, ok := data.(*User); ok && v.UserName == "admin" {
return true
}
return false
},
Unauthorized: func(c *gin.Context, code int, message string) {
c.JSON(code, gin.H{
"code": code,
"message": message,
})
},
TokenLookup: "header: Authorization, query: token, cookie: jwt_middleware",
TokenHeadName: "Bearer",
TimeFunc: time.Now,
})
return
}
以上程式碼在基於gin的golang web開發:認證利器jwt一文中有詳細的解釋,我們重點來看一下使用者驗證的部分:Authenticator
。
方法ShouldBind
對引數進行模型繫結,不熟悉模型繫結的話可以檢視前文基於gin的golang web開發:模型繫結。然後呼叫FindUser
方法檢查使用者是否存在,如果使用者存在的話還需要驗證一下使用者密碼是否正確。全部驗證通過返回User
結構體,進入gin-jwt
的後續流程。
最後一步在Gin中增加使用者登入路由。
func main() {
r := gin.Default()
authMiddleware, err := JwtMiddleware()
if err != nil {
log.Fatal("JWT Error:" + err.Error())
}
errInit := authMiddleware.MiddlewareInit()
if errInit != nil {
log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
}
r.POST("/login", authMiddleware.LoginHandler)
r.Run(":8090")
}
大功告成,現在呼叫/login
介面,並提供正確的使用者名稱和密碼,不出意外的話會得到一個成功的JSON,裡面包含了驗證通過的JWT。