基于gin的golang web开发:实现用户登录
前文分别介绍过了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。