Day21 Gin with JWT

Gin with JWT

使用Gin框架整合JWT

在Golang语言中, jwt-go 库提供了一些jwt编码和验证的工具,因此我们很容易使用该库来实现token认证。

另外,我们也知道 gin 框架中支援使用者自定义middleware,我们可以很好的将jwt相关的逻辑封装在middleware中,然後对具体的介面进行认证。

Installation

go get -u github.com/dgrijalva/jwt-go

Customer Claims

又可将其称作Payload,以Json的形式将使用者相关讯息,甚至是过期时间、签证发放时间都写在这

app/middleware/jwt-token.go

// MyClaims Customer jwt.StandardClaims
type MyClaims struct {
	Account string `json:"account"`
	jwt.StandardClaims
}
  • 这边我们自定义一个MyClaims,并除了jwt-go原本的jwt.StandardClaims外,我们还另外储存了Account的资讯

Generate Token

再来我们会写个产生Token的Function,用以产生用来认证的JWT Token。

app/middleware/jwt-token.go

// GenToken Create a new token
func GenToken(account string) (string, error) {
	c := MyClaims{
		account,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
			Issuer: "Flynn",
		},
	}
	// Choose specific algorithm
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
	// Choose specific Signature
	return token.SignedString(SecretKey)
}
  • 透过Account来创造自定义的Claims,然後设定过期时间为两小、发证人为Flynn
  • 再来则是将Claims选择HS256 Algorithm并加密
  • 最後则将Token加上签名

Login API

接下来则是写个Login function来让Login API的Router做使用

app/controller/user.go

// AuthHandler @Summary
// @Tags user
// @version 1.0
// @produce application/json
// @param register body Login true "login"
// @Success 200 string successful return token
// @Router /v1/users/login [post]
func (u UsersController) AuthHandler(c *gin.Context) {
	var form Login
	bindErr := c.BindJSON(&form)
	if bindErr != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Invalid params",
		})
		return
	}
	userOne, err := service.LoginOneUser(form.Account, form.Password)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"status": -1,
			"msg":    "Failed to parse params" + err.Error(),
			"data":   nil,
		})
	}

	if userOne == nil {
		c.JSON(http.StatusNotFound, gin.H{
			"status": -1,
			"msg":    "User not found",
			"data":   nil,
		})
	}

	if userOne != nil {
		tokenString, _ := middleware.GenToken(form.Account)
		c.JSON(http.StatusOK, gin.H{
			"code": 0,
			"msg":  "Success",
			"data": gin.H{"token": tokenString},
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"code": 0,
		"msg":  "Verified Failed.",
	})
	return
}
  • 透过body的内容去User table进行Query,若帐号密码符合的话则generate一个新的token然後return给User
  • 再来就是补上该API的swagger annotation,这部分就不在赘述。

Auth Middleware of JWT Token

再来则是写一个验证JWT Token的Middleware来给router使用

app/midddleware/jwt-token.go

// ParseToken Parse token
func ParseToken(tokenString string) (*MyClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) {
		return SecretKey, nil
	})
	if err != nil {
		return nil, err
	}
	// Valid token
	if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

// JWTAuthMiddleware Middleware of JWT
func JWTAuthMiddleware() func(c *gin.Context) {
	return func(c *gin.Context) {
		// Get token from Header.Authorization field.
		authHeader := c.Request.Header.Get("Authorization")
		if authHeader == "" {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": -1,
				"msg":  "Authorization is null in Header",
			})
			c.Abort()
			return
		}

		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": -1,
				"msg":  "Format of Authorization is wrong",
			})
			c.Abort()
			return
		}
		// parts[0] is Bearer, parts is token.
		mc, err := ParseToken(parts[1])
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": -1,
				"msg":  "Invalid Token.",
			})
			c.Abort()
			return
		}
		// Store Account info into Context
		c.Set("account", mc.Account)
		// After that, we can get Account info from c.Get("account")
		c.Next()
	}
}
  • 首先Based在自定义的Claims上写个Parse Token Function出来
  • 接下来则开始写我们的Middleware function,这边就很简单的从gin.Context的Header拿出Authorization token,接着就是对该Token进行验证,并将验证失败的地方补上Response。

Existing API with JWT

app/config/route.go

posts.GET("/:id", middleware.JWTAuthMiddleware(), controller.QueryUsersController().GetUser)
  • 在需要JWT验证的地方加上JWTAuthMiddleware()

main.go

// @title Gin swagger
// @version 1.0
// @description Gin swagger

// @contact.name Flynn Sun

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host localhost:8080
// schemes http
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
func main() {
  • 然後主程序部份别忘了补上 BearerAuth,产生新的swag template,这样Swagger页面才有地方让人输入Token

app/controller/user.go

// GetUser @Summary
// @Tags user
// @version 1.0
// @produce application/json
// @Security BearerAuth
// @param id path int true "id" default(1)
// @Success 200 string successful return data
// @Router /v1/users/{id} [get]
func (u UsersController) GetUser(c *gin.Context) {
  • 最後则是该API所对应到的controller function,记得要加上@Security BearerAuth 这行annotation,否则他会吃不到swagger的BearerAuth token

最後则记得在专案根目录输入swag init来重新产生swagger template,否则上面更改的swagger shit不会生效喔。

Demo

首先我们Login并取得有效token

https://ithelp.ithome.com.tw/upload/images/20211006/20129737WayefloGbc.png

再来,假设没有输入token就执行需要JWT验证的API,就会获得以下回应

https://ithelp.ithome.com.tw/upload/images/20211006/201297370UoO9qHGVB.png

因此我们需要到swagger页面上方找到 Authorization Button,并输入我们的token:

Bearer <token>

https://ithelp.ithome.com.tw/upload/images/20211006/20129737Zc7EgyhnmS.png

接着就可以再次地去执行需要JWT验证的API了

https://ithelp.ithome.com.tw/upload/images/20211006/201297372aRi4ldPWX.png

Summary

这章节我们透过实战来将jwt-go与现有的後端程序码进行结构,从middleware、router、controller再到swagger都实装了一遍,如果有细节不懂者可以参考下方连结,这次的code我也会放在这。

https://github.com/Neskem/Ironman-2021/tree/Day-21


<<:  Day 22 中断的运用

>>:  Unity与Photon的新手相遇旅途 | Day21-Photon Lobby UI (上)

Python - PySparkPracticeQuestions - PySpark 练习题参考笔记

Python - PySparkPracticeQuestions - PySpark 练习题参考笔...

Day27:测试 Coroutine

Coroutine 是非同步程序的解决方案,我们将耗时的任务置放在 suspend 函式中,在正常的...

Day 1 - 启程

时间回到刚毕业时的我,对於未来想从事的工作主要从过去的实习经验以及本身觉得有兴趣的领域寻找,而在系列...

Day 28: 人工智慧在音乐领域的应用 (伦敦-Jukedeck、纽约-Amper Music 、OpenAI-Jukebox)

今天我们继续介绍一些比较知名的AI作曲的公司/软件。 Jukedeck Jukedeck可以说是AI...