基于gin框架和jwt-go中间件实现小程序用户登陆和token验证

本文核心内容是利用jwt-go中间件来开发golang webapi用户登陆模块的token下发和验证,小程序登陆功能只是一个切入点,这套逻辑同样适用于其他客户端的登陆处理。

小程序登陆逻辑

小程序的登陆逻辑在其他博主的文章中已经总结得非常详尽,比如我参考的是这篇博文:微信小程序登录逻辑整理,所以在这里不再赘述,只是大致归纳一下我的实现流程:

  1. 在小程序端调用wx.login方法,异步获得到微信下发的 jscode ,然后将 jscode 发送到 golang 服务端(如果需要详细用户信息,见参考博文的实现逻辑,流程大致相似);

  2. 服务端接收到 jscode 后,将其与 AppID 和 AppSecret 一起按官方文档的格式,发送到微信接口( AppID 和 AppSecret 在小程序管理平台上进行查看),如果接口调用成功,会返回以下字段:
    • openid:用户信息唯一识别id
    • session_key :解密用户信息的key
    • expires_in :key的有效期
  3. 根据 open_id 去数据库查找对应的用户信息,如果有,获得其平台 uid ,否则新建用户,返回新建信息 uid ;
  4. 将 uid 作为关键信息,生成 jwt 格式的 token 字符串,返回给小程序客户端,小程序收到 token 判定登陆成功,并将 token 存入 localstorage ,以后的每次请求先读取 localstorage 中的 token 放入请求头部作为身份标识,如果 token 失效或者无法读取,则重新执行登陆流程。

由于小程序段逻辑简单,而且不是本文讨论重点,代码实现就不贴出了,后面应该也不会再补。

服务端处理流程

服务端实现是本文的重头戏,由于 golang 语言的特性,我在实际开发中也踩了一些不大不小的坑,在这里进行详细记录,也作为一个经验总结,同时加深印象。服务端流程可以大致分为以下几个大步骤:

  1. 根据客户端发送的 jscode 获得用户 open_id

  2. 利用 open_id 获取平台 uid ,同时使用 jwt-go 中间件实现 token 的生成

  3. 封装 token 验证中间件,判断请求是否合法

本文的代码实现是在gin 框架基础上完成的,gin是一个非常轻量的 web http 处理框架,很符合 golang 轻框架的理念,但高度灵活也要求了一定的自主开发能力,比如请求数据库读写、请求信息读取和一些中间件的使用,这些都需要自己查找不同包的官方文档,去检索Api和查找对应的解决方案。虽然如此,但作为习惯了 .net平台 高度封装和甜到发腻的语法糖的 .neter ,在 golang 开发过程中也体会到了不一样的乐趣。废话不多说,如果对 golang 开发感兴趣的话,gin是一个我十分推荐的上手框架。

获得用户 open_id

在这里简单介绍一下路由、model和controller的一个分层开发实现:

  • main.go 程序入口,在这里进行路由分发
  • controllers/xx.go xx模块的路由请求处理代码相关
  • models/xx.go xx模块用到的结构体 struct (类似class)定和结构体相关函数定义
  • middleware 存放封装后的请求处理中间件

首先,按照gin框架的基础路由处理,调用Controller中的登陆函数,接收处理路由的 GET 请求。

//main.go
...
func main(){
    r := gin.Default()
    account := new(controllers.AccountController)
    r.GET("/account/login", account.WxLogin)
}

然后在Controller中利用c.Query读取参数 jscode ,再将 jscode 和其他信息一起发送给微信服务器,获得官方返回的核心字段。

...
//接受请求参数后进行处理
func (ctrl AccountController) WxLogin(c *gin.Context) {
     jscode := c.Query("jsCode")
    //发送jscode,获得用户的open_id
    wxSession, err := accountModel.WxLogin(jscode)
    ...
}

具体实现jscode发送和处理的逻辑在Model中完成(顺便吐槽一下golang的错误处理,写了无数的if err!=nil ),还有golang 结构体中的tag十分好用,绑定数据库读写实体、json序列化字段都能用一个结构体和不同tag灵活处理。


//WxSession 微信登陆接口返回session
type WxSession struct {
    SessionKey string `json:"session_key"`
    ExpireIn   int    `json:"expires_in"`
    OpenID     string `json:"openid"`
}


//WxLogin 微信用户授权
func (m AccountModel) WxLogin(jscode string) (session WxSession, err error) {
    client := &http.Client{}

    //生成要访问的url
    url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", "xxxYOUR APPIDxxx", "xxxYOUR SECRETxxx", jscode)

    //提交请求
    reqest, err := http.NewRequest("GET", url, nil)

    if err != nil {
        panic(err)
    }

    //处理返回结果
    response, _ := client.Do(reqest)

    body, err := ioutil.ReadAll(response.Body)

    jsonStr := string(body)
    //解析json
    if err := json.Unmarshal(body, &session); err != nil {
        session.SessionKey = jsonStr
        return session, err
    }

    return session, err
}

返回的session中即包含open_id

利用jwt-go生成token

拿到open_id后就可以根据open_id去数据库查询所绑定的用户id,得到用户身份,同时也需要根据用户信息来生成token。而我需要的只是uid,其他用户信息的封装原理类似,仅供参考。

jwt-go是个功能强大的jwt生成包,封装了很多定制化函数,可以根据实际需要灵活的配置信息来生成符合要求的token字符串,并且提供了token自动验证的功能,详细说明见GitHub主页:jwt-to


//SignWxToken 生成token,uid用户id,expireSec过期秒数
func (u Util) SignWxToken(uid int64, expireSec int) (tokenStr string, err error) {
    // 带权限创建令牌
    claims := make(jwt.MapClaims)
    claims["uid"] = uid
    claims["admin"] = false

    sec := time.Duration(expireSec)
    claims["exp"] = time.Now().Add(time.Second * sec).Unix() //自定义有效期,过期需要重新登录获取token
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    // 使用自定义字符串加密 and get the complete encoded token as a string
    tokenStr, err = token.SignedString([]byte("xxxYOUR KEYxxx"))

    return tokenStr, err
}

这里生成标准格式的jwt字符串,将token下发到小程序客户端后,客户端将在每次请求携带此token,来进行身份校验。这里建议小程序端将所有的request进行集合封装,以便于统一操作,这个项目开发完后也会对小程序端的一些操作进行总结概括,这里不多废话,token需要存放在请求头部的Authorization中。

token验证中间件

首先,在main.go添加权限验证路由组:

    uAuth := r.Group("/xxx", jwtauth.WxAuth())
    {
       .......
       这里是所有需要用户身份识别的路由
       .......
    }

然后写验证中间件:

type MyCustomClaims struct {
    UID int `json:"uid"`
    jwt.StandardClaims
}


//WxAuth ...
func WxAuth() gin.HandlerFunc {
    return func(c *gin.Context) {

        authString := c.Request.Header.Get("Authorization")

        kv := strings.Split(authString, " ")
        if len(kv) != 2 || kv[0] != "Bearer" {
            result := models.UnauthorizedResult()
            c.JSON(200, result)
            c.Abort()
            return
        }

        tokenString := kv[1]

        // Parse token
        token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
            return []byte("xxxYOUR KEYxxx"), nil
        })

        if err != nil {
            result := models.UnauthorizedResult()
            c.JSON(200, result)
            c.Abort()
            return
        }

        if !token.Valid {
            result := models.UnauthorizedResult()
            c.JSON(200, result)
            c.Abort()
            return
        }
        claims, ok := token.Claims.(*MyCustomClaims)

        if !ok {
            c.JSON(403, result)
            c.Abort()
            return
        }
        //将uid写入请求参数
        uid := claims.UID
        c.Set("uid", uid)

    }
}

其中result是自己封装的一个返回值结构体,因为UID是自己额外封装的参数,这里根据jwt-go的说明,封装了一个结构体存放和解析参数,实际上如果用原生默认方法也是可以取到这个值,但是要添加额外的解析处理,这里还是推荐用封装结构体的方法。最后token验证成功的话,将uid写入请求内部参数,供权限组内的路由函数使用,示例如下:

//List 列表
func (ctrl XXXController) List(c *gin.Context) {
    uidVal, ok := c.Get("uid")
    if ok {
        uid := uidVal.(int)
         ....
          根据UID进行其他操作
         ....
    }
   

至此,一个简单的从小程序登陆和api用户认证的流程已经完成。

总结

写到这里,发现这篇博文更像是一篇流水账,把自己的实现逻辑和代码记录了一下,对详细的实现原理和一些细节操作,没有花很大篇幅去写明白,原因之一是精力有限,业务功能还没做完,不太可能花很多时间来介绍一个登陆模块,这里只是简单梳理进行经验共享,主要原因还是自己功力不够,对原理实现这一块没有深入了解过,目前更多关注的只是业务实现,对golang的运行机制和一些包的使用原理了解不够,这些都需要慢慢加强,希望以后能做做一点学习分享吧。这篇简单介绍就到此结束了,感谢阅读。

转载于:https://www.cnblogs.com/zzayne/p/8667517.html

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用Gin框架中使用JWT的过程中,可以借助GitHub上别人封装好的包来简化开发工作,比如GitHub - appleboy/gin-jwt: JWT Middleware for Gin framework。这个包提供了JWT身份验证中间件,并且还包含其他处理程序功能,比如生成令牌的登录API和刷新令牌的处理程序。你可以通过自定义Claims来实现对JWT的个性化设置,同时还需要进行前期准备,生成和解析JWT令牌。总之,使用这个JWT中间件可以使得在Gin框架中使用JWT更加方便和安全。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [在gin框架中使用JWT](https://blog.csdn.net/m0_56137272/article/details/127121590)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [在Gin框架中使用JWT](https://blog.csdn.net/Gherbirthday0916/article/details/129517817)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [gin-jwt:JWT Gin中间件框架](https://download.csdn.net/download/weixin_42169674/18199212)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值