JWT validation middleware

1. The contrastcookie,session,token,jwt

Because HTTP is a stateless protocol, there is no way to prove that “you are still you” when switching pages, so in order to save some state or information, there are these schemes:

  • cookie

It is generated by the server and sent to the browser, which saves it in the form of key-value pairs and sends the cookie saved information to the client server when sending the request next time.

Disadvantages: each domain name can be used under the number of small, size is limited.

  • session

Generated by the server, the server saves the subject information and sends a sessionID to the client for cookie saving. The next time the request is sent, the sessionID is sent to the server. The server compares and verifies the sessionID with the subject information.

Disadvantages:

  • Cookie +session is cumbersome in cross-domain scenarios;
  • If the system is deployed in distributed mode, the session mechanism must be shared by multiple hosts.
  • Cookie-based mechanisms are easily CSRF;
  • Querying session information may cause database query operations, which may cause performance problems.
  • token

After the user sends the username and password to the server, the server verifies that the request is successful and returns a token to the client. The server verifies that the token is valid for each subsequent request. Generally, the token is saved in the database.

Disadvantages: The client carries the token each time, saving too much content, and may involve the operation of checking the database.

  • JWT

The user sends the username and password to the server for verification. After the authentication, the server generates a JWT token consisting of header,payload, and signature to the client. All subsequent requests carry a JWT token and the server authenticates them with the Secret key.

Disadvantages: can not be abandoned halfway

2. Golang JWT validation

2.1. JWT third-party libraries used

github.com/dgrijalva/jwt-go
Copy the code

2.2. Generate JWT tokens

type MyClaims struct {
   Username string `json:"username"`
   jwt.StandardClaims
}

const TokenExpireDuration = time.Hour * 24 // Set the expiration time

var Secret = []byte("secret") // Set the password by yourself

func GenToken(username string) (string, error) {
   // Create a declaration of our own
   c := MyClaims{
      username, // Customize the field
      jwt.StandardClaims{
         ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // Expiration time
         Issuer:    "superxon"./ / issue}},// Creates a signature object using the specified signature method
   token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
   // Use the specified secret signature and get the full encoded string token
   return token.SignedString(Secret)
}
Copy the code

2.3. Verify the user password and return the generated JWT token to the client for saving

type Profile struct {
   Username    string `db:"username"`
   Password    string `db:"password"`
}
Copy the code
func AuthLoginHandler(c *gin.Context) {
   // The user sends the username and password
   var user User.Profile
   err := c.ShouldBindJSON(&user)
   iferr ! =nil {
      c.JSON(http.StatusBadRequest, gin.H{
         "code": 2001."msg":  "Invalid parameter",})return
   }
   // Verify the user name and password
   _, err := getProfile(user.Username)
   iferr ! =nil {
       c.JSON(http.StatusNotFound, gin.H{
          "code": 2003."msg":  "User does not exist",})return
   }

   tokenString, _ := Middlewares.GenToken(user.Username)
   c.JSON(http.StatusOK, gin.H{
       "code":     2000."msg":      "success"."Token":    tokenString,
       "username": profile.Username,
   })
}
Copy the code

2.4. Put Headers into the requestauthorization= JWT token) Middleware for JWT verification

Parsing the token

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

Verification middleware

// JWTAuthMiddleware jWT-BASED authentication middleware -- verifies that a user is logged in
func JWTAuthMiddleware(a) func(c *gin.Context) {
   return func(c *gin.Context) {
      authHeader := c.Request.Header.Get("authorization")
      if authHeader == "" {
         c.JSON(http.StatusUnauthorized, gin.H{
            "code": 2003."msg":  "Request header auth is empty",
         })
         c.Abort()
         return
      }
      // Split by space
      parts := strings.Split(authHeader, ".")
      if len(parts) ! =3 {
         c.JSON(http.StatusUnauthorized, gin.H{
            "code": 2004."msg":  "Incorrect auth format in request header",
         })
         c.Abort()
         return
      }
      mc, ok := ParseToken(authHeader, Secret)
      if ok == false {
         c.JSON(http.StatusUnauthorized, gin.H{
            "code": 2005."msg":  "Invalid Token",
         })
         c.Abort()
         return
      }
      m := mc.(jwt.MapClaims)
      // Save the current request username information to the request context C
      c.Set("username", m["username"])
      c.Next() // Subsequent handlers can use c.net ("username") to get the current requested user information}}Copy the code

2.5. You can test the difference between using JWT middleware in routing and not using it.

Traffic limiting middleware

1. Reference documentsCurrent-limiting middleware

2. Application Scenario To prevent the server from being overwhelmed by sudden increase in concurrency, the upper limit of QPS is guaranteed.

3. It is mainly divided into three partsbucketandThe token bucket:

A leaky bucket is when we have a bucket that is always full of water and leaks out a drop of water every fixed period of time. If you receive the drop, then you can continue the service request, if not, then you need to wait for the next drop.

Token bucket refers to adding tokens to the bucket at a constant speed. When service requests are made, tokens need to be obtained from the bucket. The number of tokens can be adjusted according to the resources consumed. If there is no token, you can either wait or give up.

4. Golang third-party library

github.com/juju/ratelimit

5. Sample code

package main

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/juju/ratelimit"
)

func RateLimitMiddleware(fillInterval time.Duration, cap, quantum int64) gin.HandlerFunc {
	bucket := ratelimit.NewBucketWithQuantum(fillInterval, cap, quantum)
	return func(c *gin.Context) {
		if bucket.TakeAvailable(1) < 1 {
			c.String(http.StatusForbidden, "rate limit...")
			c.Abort()
			return
		}
		c.Next()
	}
}

func main(a) {
	r := gin.Default()
	gin.ForceConsoleColor()
	r.Use(RateLimitMiddleware(time.Second, 100.100)) // The initial 100 is emitted 100 per second
	r.GET("/".func(c *gin.Context) {
		c.String(http.StatusOK, "golang ~")
	})
	r.Run(": 8080")}Copy the code

6. Other methods in the library

  • The default token bucket. FillInterval refers to the amount of time that a token is added to the bucket. Capacity is the capacity of the bucket. The bucket was initially full

func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
Copy the code
  • The difference from regular NewBucket() is that each time a token is placed in the bucket, quantum tokens are placed instead of one.

func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
Copy the code
  • Fill in the number of tokens per second at the scale provided. For example, if capacity is 100 and rate is 0.1, 10 tokens will be filled every second.

func NewBucketWithRate(rate float64, capacity int64) *Bucket
Copy the code