Using JWT in gin framework

The full name of JWT, JSON Web Token, is a cross-domain authentication solution. It belongs to an open standard. It specifies a token implementation method. Currently, it is mostly used in front-end and back-end separation projects and OAuth2.0 business scenarios.

What is JWT?

The full name of JWT, JSON Web Token, is a cross-domain authentication solution. It belongs to an open standard. It specifies a token implementation method. Currently, it is mostly used in front-end and back-end separation projects and OAuth2.0 business scenarios.

Why do you need JWT?

In some previous web projects, we usually use the Cookie-Session mode to implement user authentication. The relevant process is roughly as follows:

  1. The user fills in the username and password on the browser and sends it to the server
  2. After the server passes the user name and password verification, it will generate a session data that saves the current user related information and a corresponding identifier (usually called session_id)
  3. When the server returns a response, the session_id of the previous step is written into the Cookie of the user's browser
  4. Each subsequent user request from the browser will automatically carry a Cookie containing session_id
  5. The server can find the previously saved session data of the user through the session_id in the request, so as to obtain the relevant information of the user.

This scheme relies on the client (browser) to save the Cookie, and needs to store the user's session data on the server.

In the era of mobile Internet, our users may use browsers or APP s to access our services. Our web applications may be deployed on different ports at the front and back ends. Sometimes we also need to support third-party login. -Session mode is a bit powerless.

JWT is a lightweight authentication mode based on Token. After the server is authenticated, a JSON object will be generated. After signing, a Token (token) will be obtained and sent back to the user. The user only needs to bring this Token in subsequent requests. , the server can obtain the relevant information of the user after decryption.

If you want to connect to the principle of JWT, it is recommended that you read: Ruan Yifeng's JWT introductory tutorial

Generate JWT and parse JWT

We directly use the jwt-go library here to implement our functions of generating JWT and parsing JWT.

define requirements

We need to customize our own requirements to decide which data to save in JWT. For example, if we stipulate that username information should be stored in JWT, then we define a MyClaims structure as follows:

// MyClaims Custom declaration structure and inline jwt.StandardClaims
// jwt included in the package jwt.StandardClaims Only official fields are included
// We need to record an additional username field, so you need to customize the structure
// If you want to save more information, you can add it to this structure
type MyClaims struct {
    Username string `json:"username"`
    jwt.StandardClaims
}

Then we define the expiration time of the JWT, here is an example of 2 hours:

const TokenExpireDuration = time.Hour * 2

Next, you need to define the Secret:

var MySecret = []byte("summer is passing by")

Generate JWT

// GenToken generate JWT
func GenToken(username string) (string, error) {
    // Create a statement of our own
    c := MyClaims{
        "username", // custom field
        jwt.StandardClaims{
            ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // Expiration
            Issuer:    "my-project",                               // signer
        },
    }
    // Create a signature object using the specified signature method
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
    // use the specified secret Sign and get the full encoded string token
    return token.SignedString(MySecret)
}

Parse JWT

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

Using JWT in gin framework

First, we register a route/auth to provide a channel for obtaining Token externally:

r.POST("/auth", authHandler)

Our authHandler is defined as follows:

func authHandler(c *gin.Context) {
    // User sends username and password
    var user UserInfo
    err := c.ShouldBind(&user)
    if err != nil {
        c.JSON(http.StatusOK, gin.H{
            "code": 2001,
            "msg":  "invalid parameter",
        })
        return
    }
    // Verify that the username and password are correct
    if user.Username == "q1mi" && user.Password == "q1mi123" {
        // generate Token
        tokenString, _ := GenToken(user.Username)
        c.JSON(http.StatusOK, gin.H{
            "code": 2000,
            "msg":  "success",
            "data": gin.H{"token": tokenString},
        })
        return
    }
    c.JSON(http.StatusOK, gin.H{
        "code": 2002,
        "msg":  "Authentication failed",
    })
    return
}

After the user obtains the Token through the above interface, the user will carry the Token and then request our other interfaces. At this time, it is necessary to verify the Token of these requests. Obviously, we should implement a middleware for verifying the Token. Specifically The implementation is as follows:

// JWTAuthMiddleware based on JWT authentication middleware
func JWTAuthMiddleware() func(c *gin.Context) {
    return func(c *gin.Context) {
        // There are three ways for the client to carry the Token: 1. Put it in the request header 2. Put it in the request body 3.put URI
        // Suppose here Token put Header of Authorization , and use Bearer beginning
        // The specific implementation method here depends on your actual business situation.
        authHeader := c.Request.Header.Get("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusOK, gin.H{
                "code": 2003,
                "msg":  "in the request header auth Is empty",
            })
            c.Abort()
            return
        }
        // split by space
        parts := strings.SplitN(authHeader, " ", 2)
        if !(len(parts) == 2 && parts[0] == "Bearer") {
            c.JSON(http.StatusOK, gin.H{
                "code": 2004,
                "msg":  "in the request header auth wrong format",
            })
            c.Abort()
            return
        }
        // parts[1]is obtained tokenString,We use the previously defined parsing JWT function to parse it
        mc, err := ParseToken(parts[1])
        if err != nil {
            c.JSON(http.StatusOK, gin.H{
                "code": 2005,
                "msg":  "Invalid Token",
            })
            c.Abort()
            return
        }
        // the current request username Information is saved to the request context c superior
        c.Set("username", mc.Username)
        c.Next() // Subsequent processing functions can use c.Get("username")to get the user information of the current request
    }
}

Register a /home route and send a request to verify it.

r.GET("/home", JWTAuthMiddleware(), homeHandler)

func homeHandler(c *gin.Context) {
    username := c.MustGet("username").(string)
    c.JSON(http.StatusOK, gin.H{
        "code": 2000,
        "msg":  "success",
        "data": gin.H{"username": username},
    })
}

If you don't want to implement the above functions yourself, you can also use packages packaged by others on Github, such as https://github.com/appleboy/gin-jwt.

 

Posted by haku on Sat, 14 May 2022 20:15:24 +0300