JSON Web Token(JWT)是一种很流行的跨域认证解决方案,JWT基于JSON可以在进行验证的同时附带身份信息,对于前后端分离项目很有帮助。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT由三部分组成,每个部分之间用点.隔开,分别称为HEADER、PAYLOAD和VERIFY SIGNATURE。HEADER和PAYLOAD经过base64解码后为JSON明文。

  1. HEADER包含两个字段,alg指明JWT的签名算法,typ固定为JWT
  2. PAYLOAD中包含JWT的声明信息,标准中定义了isssubaud等声明字段,如果标准声明不够用的话,我们还可以增加自定义声明。要注意两点,第一PAYLOAD只是经过base64编码,几乎就等于是明文,不要包含敏感信息。第二不要在PAYLOAD中放入过多的信息,因为验证通过以后每一个请求都要包含JWT,信息太多的话会造成一些没有必要的资源浪费。
  3. VERIFY SIGNATURE为使用HEADER中指定的算法生成的签名。例如alg:HS256签名算法HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),密钥)

了解完JWT的基本原理之后,我们来看一下在gin中是怎么使用JWT的。

引入gin-jwt中间件

在Gin中使用jwt有个开源项目gin-jwt,这项目几乎包含了我们要用到的一切。例如定义PAYLOAD中的声明、授权验证的方法、是否使用COOKIE等等。下面来看一下官网给出的例子。

package main

import (
"log"
"net/http"
"os"
"time" jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-gonic/gin"
) type login struct {
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
} var identityKey = "id" func helloHandler(c *gin.Context) {
claims := jwt.ExtractClaims(c)
user, _ := c.Get(identityKey)
c.JSON(200, gin.H{
"userID": claims[identityKey],
"userName": user.(*User).UserName,
"text": "Hello World.",
})
} type User struct {
UserName string
FirstName string
LastName string
} func main() {
port := os.Getenv("PORT")
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery()) if port == "" {
port = "8000"
} 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 if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
return &User{
UserName: userID,
LastName: "Bo-Yi",
FirstName: "Wu",
}, nil
} return nil, jwt.ErrFailedAuthentication
},
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",
TokenHeadName: "Bearer",
TimeFunc: time.Now,
}) 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.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
claims := jwt.ExtractClaims(c)
log.Printf("NoRoute claims: %#v\n", claims)
c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
}) auth := r.Group("/auth")
auth.GET("/refresh_token", authMiddleware.RefreshHandler)
auth.Use(authMiddleware.MiddlewareFunc())
{
auth.GET("/hello", helloHandler)
} if err := http.ListenAndServe(":"+port, r); err != nil {
log.Fatal(err)
}
}

我们可以看到jwt.GinJWTMiddleware用于声明一个中间件。PayloadFunc方法中给默认的PAYLOAD增加了id字段,取值为UserName。Authenticator认证器,我们可以在这里验证用户身份,参数为*gin.Context,所以在这里我们可以像写Gin Handler那样获取到Http请求中的各种内容。Authorizator授权器可以判断判断当前JWT是否有权限继续访问。当然还可以设置像过期时间,密钥,是否设置COOKIE等其他选项。

登录Handler

以上例子中配置了路由r.POST("/login", authMiddleware.LoginHandler)下面我们来看一下登录过程是怎样的。

func (mw *GinJWTMiddleware) LoginHandler(c *gin.Context) {
if mw.Authenticator == nil {
mw.unauthorized(c, http.StatusInternalServerError, mw.HTTPStatusMessageFunc(ErrMissingAuthenticatorFunc, c))
return
} data, err := mw.Authenticator(c) if err != nil {
mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, c))
return
} // Create the token
token := jwt.New(jwt.GetSigningMethod(mw.SigningAlgorithm))
claims := token.Claims.(jwt.MapClaims) if mw.PayloadFunc != nil {
for key, value := range mw.PayloadFunc(data) {
claims[key] = value
}
} expire := mw.TimeFunc().Add(mw.Timeout)
claims["exp"] = expire.Unix()
claims["orig_iat"] = mw.TimeFunc().Unix()
tokenString, err := mw.signedString(token) if err != nil {
mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrFailedTokenCreation, c))
return
} // set cookie
if mw.SendCookie {
expireCookie := mw.TimeFunc().Add(mw.CookieMaxAge)
maxage := int(expireCookie.Unix() - mw.TimeFunc().Unix()) if mw.CookieSameSite != 0 {
c.SetSameSite(mw.CookieSameSite)
} c.SetCookie(
mw.CookieName,
tokenString,
maxage,
"/",
mw.CookieDomain,
mw.SecureCookie,
mw.CookieHTTPOnly,
)
} mw.LoginResponse(c, http.StatusOK, tokenString, expire)
}

LoginHandler整体逻辑还是比较简单的,检查并调用前面设置的Authenticator方法,验证成功的话生成一个新的JWT,调用PayloadFunc方法设置PAYLOAD的自定义字段,根据SendCookie判断是否需要在HTTP中设置COOKIE,最后调用LoginResponse方法设置返回值。

使用中间件

jwt-gin包提供了一个标准的Gin中间件,我们可以在需要验证JWT的路由上设置中间件。前面例子中对路由组/auth增加了JWT验证auth.Use(authMiddleware.MiddlewareFunc())

func (mw *GinJWTMiddleware) MiddlewareFunc() gin.HandlerFunc {
return func(c *gin.Context) {
mw.middlewareImpl(c)
}
} func (mw *GinJWTMiddleware) middlewareImpl(c *gin.Context) {
claims, err := mw.GetClaimsFromJWT(c)
if err != nil {
mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, c))
return
} if claims["exp"] == nil {
mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrMissingExpField, c))
return
} if _, ok := claims["exp"].(float64); !ok {
mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrWrongFormatOfExp, c))
return
} if int64(claims["exp"].(float64)) < mw.TimeFunc().Unix() {
mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrExpiredToken, c))
return
} c.Set("JWT_PAYLOAD", claims)
identity := mw.IdentityHandler(c) if identity != nil {
c.Set(mw.IdentityKey, identity)
} if !mw.Authorizator(identity, c) {
mw.unauthorized(c, http.StatusForbidden, mw.HTTPStatusMessageFunc(ErrForbidden, c))
return
} c.Next()
}

GetClaimsFromJWT方法在当前上下文中获取JWT,失败的话返回未授权。接着会判断JWT是否过期,最后前面设置的Authorizator方法验证是否有权限继续访问。

文章出处:基于gin的golang web开发:认证利器jwt

基于gin的golang web开发:认证利器jwt的更多相关文章

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

    前文分别介绍过了Resty和gin-jwt两个包,Resty是一个HTTP和REST客户端,gin-jwt是一个实现了JWT的Gin中间件.本文将使用这两个包来实现一个简单的用户登录功能. 环境准备 ...

  2. 基于gin的golang web开发:中间件

    gin中间件(middleware)提供了类似于面向切面编程或路由拦截器的功能,可以在请求前和请求之后添加一些自定义逻辑.实际开发中有很多场景会用到中间件,例如:权限验证,缓存,错误处理,日志,事务等 ...

  3. 基于gin的golang web开发:路由

    Gin是一个用Golang编写的HTTP网络框架.它的特点是类似于Martini的API,性能更好.在golang web开发领域是一个非常热门的web框架. 启动一个Gin web服务器 使用下面的 ...

  4. 基于gin的golang web开发:路由二

    在基于gin的golang web开发:路由中我们介绍了Gin的路由和一些获取链接中参数的方法,本文继续介绍其他获取参数的方法. 文件上传 在web开发中文件上传是一个很常见的需求,下面我们来看一下基 ...

  5. 基于gin的golang web开发:模型绑定

    在前两篇文章介绍路由的时候,我们了解到gin可用通过类似DefaultQuery或DefaultPostForm等方法获取到前端提交过来的参数.参数不多的情况下也很好用,但是想想看,如果接口有很多个参 ...

  6. 基于gin的golang web开发:模型验证

    Gin除了模型绑定还提供了模型验证功能.你可以给字段指定特定的规则标签,如果一个字段用binding:"required"标签修饰,在绑定时该字段的值为空,那么将返回一个错误.开发 ...

  7. 基于gin的golang web开发:访问mysql数据库

    web开发基本都离不开访问数据库,在Gin中使用mysql数据库需要依赖mysql的驱动.直接使用驱动提供的API就要写很多样板代码.你可以找到很多扩展包这里介绍的是jmoiron/sqlx.另外还有 ...

  8. 基于gin的golang web开发:使用数据库事务

    在前文介绍访问数据库时介绍了github.com/jmoiron/sqlx包,本文基于这个包使用数据库事务. defer 在使用数据库事务之前,首先需要了解go语言的defer关键字.defer是go ...

  9. 基于gin的golang web开发:mysql增删改查

    Go语言访问mysql数据库需要用到标准库database/sql和mysql的驱动.标准库的Api使用比较繁琐这里再引入另一个库github.com/jmoiron/sqlx. go get git ...

随机推荐

  1. 4G DTU无线数据透明传输终端

    4G DTU是基于4G网络的远程无线数据透明传输终端,是一种物联网无线数据传输设备,使用公用运营商的4G网络为用户提供无线远距离数据传输功能,使用工业级32位的高性能通信处理器和工业级无线模块,以嵌入 ...

  2. SQLServer连接cache数据库

    开始文章之前首先要了解一下什么是Caché数据库. Caché数据库是美国Intersystems公司产品,后关系型数据库(Post Relational database)中的领头羊.Caché数据 ...

  3. Java_Stringbuilder和StringBuffer

    StringBuilder和StringBuffer非常类似, 均代表可变的字符串序列. 这两个类都是抽线类AbstractStringBuilder的子类, 方法几乎一样 /******String ...

  4. Vue的生命周期--代码片段

    Vue 实例有一个完整的生命周期,也就是从开始创建. 初始化数据. 编译模板. 挂载 Dom. 渲染→更新→渲染. 销毁等一系列过程,我们称这是 Vue 的生命周期.通俗说就是 Vue 实例从创建到销 ...

  5. sdasd

    create PROCEDURE v4(in c_year int) BEGIN declare num int(10) default 0; declare num1 int(10); select ...

  6. 1、Web应用

    一 Web应用的组成 接下来我们学习的目的是为了开发一个Web应用程序,而Web应用程序是基于B/S架构的,其中B指的是浏览器,负责向S端发送请求信息,而S端会根据接收到的请求信息返回相应的数据给浏览 ...

  7. leetcode111:combination-sum

    题目描述 给出一组候选数C和一个目标数T,找出候选数中加起来和等于T的所有组合. C中的数字在组合中可以被无限次使用 注意: 题目中所有的数字(包括目标数T)都是正整数 你给出的组合中的数字 (a 1 ...

  8. Pycharm激活码,2020年9月29日最新激活码

    分享一个Pycharm激活码给大家: 5MJ8MJ2T1Q-eyJsaWNlbnNlSWQiOiI1TUo4TUoyVDFRIiwibGljZW5zZWVOYW1lIjoi6I635Y+W77yaIG ...

  9. pc端兼容IE9及以上版本

    最近业务部门反映我们商城的兼容性不是很好,尤其是在IE浏览器上,经过调研,我们决定对IE9及以上版本的IE内核浏览器进行主流程测试,发现有哪些功能在IE9上不兼容 一.CSS兼容性 如下图所示 使用了 ...

  10. spark推测机制及参数设置

    推测执行机制 推测任务是指对于一个Stage里面拖后腿的Task,会在其他节点的Executor上再次启动这个task,如果其中一个Task实例运行成功则将这个最先完成的Task的计算结果作为最终结果 ...