本文核心内容是利用jwt-go中间件来开发golang webapi用户登陆模块的token下发和验证,小程序登陆功能只是一个切入点,这套逻辑同样适用于其他客户端的登陆处理。
小程序登陆逻辑
小程序的登陆逻辑在其他博主的文章中已经总结得非常详尽,比如我参考的是这篇博文:微信小程序登录逻辑整理,所以在这里不再赘述,只是大致归纳一下我的实现流程:
在小程序端调用
wx.login
方法,异步获得到微信下发的 jscode ,然后将 jscode 发送到 golang 服务端(如果需要详细用户信息,见参考博文的实现逻辑,流程大致相似);-
服务端接收到 jscode 后,将其与 AppID 和 AppSecret 一起按官方文档的格式,发送到微信接口( AppID 和 AppSecret 在小程序管理平台上进行查看),如果接口调用成功,会返回以下字段:
- openid:用户信息唯一识别id
- session_key :解密用户信息的key
- expires_in :key的有效期
根据 open_id 去数据库查找对应的用户信息,如果有,获得其平台 uid ,否则新建用户,返回新建信息 uid ;
将 uid 作为关键信息,生成 jwt 格式的 token 字符串,返回给小程序客户端,小程序收到 token 判定登陆成功,并将 token 存入 localstorage ,以后的每次请求先读取 localstorage 中的 token 放入请求头部作为身份标识,如果 token 失效或者无法读取,则重新执行登陆流程。
由于小程序段逻辑简单,而且不是本文讨论重点,代码实现就不贴出了,后面应该也不会再补。
服务端处理流程
服务端实现是本文的重头戏,由于 golang 语言的特性,我在实际开发中也踩了一些不大不小的坑,在这里进行详细记录,也作为一个经验总结,同时加深印象。服务端流程可以大致分为以下几个大步骤:
根据客户端发送的 jscode 获得用户 open_id
利用 open_id 获取平台 uid ,同时使用 jwt-go 中间件实现 token 的生成
封装 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的运行机制和一些包的使用原理了解不够,这些都需要慢慢加强,希望以后能做做一点学习分享吧。这篇简单介绍就到此结束了,感谢阅读。
基于gin框架和jwt-go中间件实现小程序用户登陆和token验证的更多相关文章
-
使用Shiro+JWT完成的微信小程序的登录(含讲解)
使用Shiro+JWT完成的微信小程序的登录 源码地址https://github.com/Jirath-Liu/shiro-jwt-wx 微信小程序用户登陆,完整流程可参考下面官方地址,本例中是按此 ...
-
基于gin的golang web开发:永远不要相信用户的输入
作为后端开发者我们要记住一句话:"永远不要相信用户的输入",这里所说的用户可能是人,也可能是另一个应用程序."永远不要相信用户的输入"是安全编码的准则,也就是说 ...
-
Django中间件 及 form 实现用户登陆
Django中间件 及 form 实现用户登陆 Form 验证 密码调用md5 加密存储 form.add_error("字段名", "错误信息") 自定义错误 ...
-
基于gin框架搭建的一个简单的web服务
刚把go编程基础知识学习完了,学习的时间很短,可能还有的没有完全吸收.不过还是在项目中发现知识,然后在去回顾已学的知识,现在利用gin这个web框架做一个简单的CRUD操作. 1.Go Web框架的技 ...
-
基于git的博客(含站点与小程序)
1 效果 静态站点: blog.makergyt.com 备用链接: github.blog.makergyt.com 小程序: 语雀:<MakerGYT blog> 2 需求分析 2.1 ...
-
基于MUI框架+HTML5PLUS 开发 iOS和Android 应用程序(APP)
目录 事前准备 创建项目 利用MUI写一个简单的页面 关于文件打包 事前准备 # 软件 HBuilder X Web开发IDE 下载地址:https://www.dcloud.io/hbuilderx ...
-
基于Node.js+MySQL开发的开源微信小程序B2C商城(页面高仿网易严选)
界面高仿网易严选商城(主要是2016年wap版) 测试数据采集自网易严选商城 功能和数据库参考ecshop 服务端api基于Node.js+ThinkJS+MySQL 计划添加基于Vue.js的后台管 ...
-
一个基于cocos2d-x 3.0和Box2d的demo小程序
p图demo小应用.想怎么p就怎么p 本文參考于http://blog.csdn.net/xiaominghimi/article/details/6776096和http://www.cnblogs ...
-
《汇编语言 基于x86处理器》前五章的小程序
▶ 书中前五章的几个小程序,基本的运算操作,使用了作者的库 Irvine32 和 Irvine64(一开始以为作者网站过期了,各网站上找到的文件大小都不一样,最后发现是要搭* Orz,顺利下载).注 ...
随机推荐
-
BinaryHeap Java实现
public class BinaryHeap<AnyType extends Comparable<? super AnyType>> { private static fi ...
-
lintcode :链表插入排序
题目: 链表插入排序 用插入排序对链表排序 样例 Given 1->3->2->0->null, return 0->1->2->3->null 解题: ...
-
面试题_lambda函数调用
res多少? def func(): return [lambda x: i * x for i in range(4)] res = [m(2) for m in func()] # print(r ...
-
@lazy注解处理循环注入问题
@Service public class A extends GenericBaseService { @Autowired private B b; } @Service public class ...
-
使用 Resharper 快速做适配器
如果需要做一个类的重写,需要重新写这个类的所有属性和函数,本文提供一个简单的方法让大家快速重写一个类的所有属性和函数. 在有 Resharper 之后,对一个比较长的类进行重构.例如有这个类 clas ...
-
自己总结的C#编码规范--前言&;目录
最近在为公司编写c#编码规范,以前对这方面研究不多,只是觉得代码能够出自己的意思就可以了. 我参考了以下资料 C# Coding Conventions NET设计规范约定惯用法与模式(第2版) 编写 ...
-
记一次性能优化,限制tcp_timewait数量,快速回收和重用
前言 这篇文章的主题是记录一次Python程序的性能优化,在优化的过程中遇到的问题,以及如何去解决的.为大家提供一个优化的思路,首先要声明的一点是,我的方式不是唯一的,大家在性能优化之路上遇到的问题都 ...
-
MySQL Gap Lock问题
四种隔离级别说明 隔离级别 脏读(Dirty Read) 不可重复读(NonRepeatable Read) 幻读(Phantom Read) 未提交读(Read uncommitted) 可能 可能 ...
-
Pandas的loc方法
当你读取到DataFrame的数据时,想去定位某一个数据项,可以使用loc方法进行查找,之后你可以赋值给他. import pandas as pd df = pd.read_csv('file_na ...
-
Python3 casefold() 方法
描述 Python casefold() 方法是Python3.3版本之后引入的,其效果和 lower() 方法非常相似,都可以转换字符串中所有大写字符为小写. 两者的区别是:lower() 方法只对 ...