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

小程序登录逻辑

小程序的登录逻辑在其余博主的文章中已经总结得很是详尽,好比我参考的是这篇博文:微信小程序登陆逻辑整理,因此在这里再也不赘述,只是大体概括一下个人实现流程:github

wx.login

因为小程序段逻辑简单,并且不是本文讨论重点,代码实现就不贴出了,后面应该也不会再补。数据库

服务端处理流程

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

  1. 根据客户端发送的 jscode 得到用户 open_id小程序

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

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

gingingin

得到用户 open_id

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

  • main.go 程序入口,在这里进行路由分发
  • controllers/xx.go xx模块的路由请求处理代码相关
  • models/xx.go xx模块用到的结构体 struct (相似class)定和结构体相关函数定义
  • middleware 存放封装后的请求处理中间件
Controller
//main.go
...
func main(){
    r := gin.Default()
    account := new(controllers.AccountController)
    r.GET("/account/login", account.WxLogin)
}
Controllerc.Query
...
//接受请求参数后进行处理
func (ctrl AccountController) WxLogin(c *gin.Context) {
     jscode := c.Query("jsCode")
    //发送jscode,得到用户的open_id
    wxSession, err := accountModel.WxLogin(jscode)
    ...
}
Modelif err!=nil
//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
//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
}
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的运行机制和一些包的使用原理了解不够,这些都须要慢慢增强,但愿之后能作作一点学习分享吧。这篇简单介绍就到此结束了,感谢阅读。