脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - 基于gin的golang web开发之认证利器jwt

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

2021-02-19 00:57陈宏博 Golang

这篇文章主要介绍了基于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等等。下面来看一下官网给出的例子。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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)下面我们来看一下登录过程是怎样的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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())

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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开发内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/huaface/p/14073330.html

延伸 · 阅读

精彩推荐
  • GolangGolang通脉之数据类型详情

    Golang通脉之数据类型详情

    这篇文章主要介绍了Golang通脉之数据类型,在编程语言中标识符就是定义的具有某种意义的词,比如变量名、常量名、函数名等等,Go语言中标识符允许由...

    4272021-11-24
  • Golanggo日志系统logrus显示文件和行号的操作

    go日志系统logrus显示文件和行号的操作

    这篇文章主要介绍了go日志系统logrus显示文件和行号的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    SmallQinYan12302021-02-02
  • Golanggo语言制作端口扫描器

    go语言制作端口扫描器

    本文给大家分享的是使用go语言编写的TCP端口扫描器,可以选择IP范围,扫描的端口,以及多线程,有需要的小伙伴可以参考下。 ...

    脚本之家3642020-04-25
  • Golanggolang如何使用struct的tag属性的详细介绍

    golang如何使用struct的tag属性的详细介绍

    这篇文章主要介绍了golang如何使用struct的tag属性的详细介绍,从例子说起,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看...

    Go语言中文网11352020-05-21
  • Golanggolang 通过ssh代理连接mysql的操作

    golang 通过ssh代理连接mysql的操作

    这篇文章主要介绍了golang 通过ssh代理连接mysql的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    a165861639710342021-03-08
  • Golanggolang json.Marshal 特殊html字符被转义的解决方法

    golang json.Marshal 特殊html字符被转义的解决方法

    今天小编就为大家分享一篇golang json.Marshal 特殊html字符被转义的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    李浩的life12792020-05-27
  • GolangGolang中Bit数组的实现方式

    Golang中Bit数组的实现方式

    这篇文章主要介绍了Golang中Bit数组的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    天易独尊11682021-06-09
  • Golanggolang的httpserver优雅重启方法详解

    golang的httpserver优雅重启方法详解

    这篇文章主要给大家介绍了关于golang的httpserver优雅重启的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    helight2992020-05-14