原创

基于gin的golang web开发:实现用户登录

前文分别介绍过了Restygin-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。

正文到此结束