• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    基于gin的golang web开发之认证利器jwt

    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开发内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

    您可能感兴趣的文章:
    • Gin golang web开发模型绑定实现过程解析
    • 基于gin的golang web开发:路由示例详解
    • golang websocket 服务端的实现
    • Golang实现web文件共享服务的示例代码
    • golang搭建静态web服务器的实现方法
    • golang基于websocket实现的简易聊天室程序
    上一篇:聊聊golang的defer的使用
    下一篇:go语言入门环境搭建及GoLand安装教程详解
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    基于gin的golang web开发之认证利器jwt 基于,gin,的,golang,web,开,