[TOC]

JWT (Json Web Token) validation

One day, it was lunch time

The sergeant passed by the seat of fat Sir And was surprised that fat Sir Did not snore today, but was concentrating on staring at a book with his head down

The soldier took a closer look and fat Sir Was actually reading history books…

Sergeant :(softly) what are you looking at

Fat Sir: I wonder if I could travel back to Qing Dynasty, what would I be?

What?? ~~~, what can he be? He must be a heavyweight

Fat Sir: I pooh, today I will tell you something about identity

Speaking of identity, I have to say the difference between cookie, session and Token, come on

1 Differences between Cookie, Session, and Token

Cookie

Cookies are always stored in the client. According to the storage location in the client, cookies can be divided into memory cookies and hard disk cookies.

The memory Cookie is maintained by the browser and stored in memory. After the browser closes, it disappears and its existence is short-lived.

Hard disk Cookies are stored in hard disks and have an expiration date. They are not deleted unless manually deleted or the expiration date is reached.

Therefore, according to the existence time, it can be divided into non-persistent cookies and persistent cookies.

So what exactly are cookies?

A cookie is a very specific thing, a type of data that can be stored permanently in a browser, just one of the many implementations of the browser

Data storage function.

The cookie is generated by the server and sent to the browser. The browser saves the cookie in key-value format to a text file in a certain directory

The cookie is sent to the server the next time the same web site is requested. Since cookies are stored on the client, the browser adds them

Some restrictions ensure that cookies cannot be used maliciously and do not take up too much disk space, so the number of cookies per field is limited.

Session

Session literally means a Session and is used to identify oneself.

For example, when the stateless API service requests the database for many times, how to know the same user can be achieved through the session mechanism. The server needs to know who is sending the request to itself. In order to distinguish the client requests, the server will generate the identity and identification session for the specific client. Each time the client sends a request to the server, it carries this “id” so that the server knows who the request came from.

As for how the client saves the identity, there are many ways. For browsers, cookies are generally used. The server uses session to temporarily store the user information on the server, and the user will be destroyed when leaving the website.

But sessions have a drawback: if the Web server is load-balanced, the session will be lost when the next operation is requested to another server.

Therefore, redis and memcached middleware are commonly used in enterprises to implement session sharing. In this case, the Web server is a completely stateless existence, and all user credentials can be accessed through shared session. The expiration and destruction mechanism of the current session requires user control.

Token

Token means “token” and is a way to verify the user’s identity. The simplest token consists of: Uid (unique user identification) + time(current timestamp) + sign(signature, hashed from the first few digits of the token + salt into a hexadecimal string of a certain length), and invariant parameters can also be put into the token

JWT (Json Web token)

What is 2 JWT?

Generally speaking, after the user registers and logs in, a JWT token is generated and returned to the browser. The browser carries the token when requesting data from the server, and the server decodes the token in the way defined in Signature to parse and verify the token.

Component of JWT token

  • Header: Used to specify the algorithm used (HMAC SHA256 RSA) and the token type (such as JWT)

    JWT libraries of various languages can be found on the official website. For example, we use this library for coding, because it is used by the most people and is trustworthy

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

  • Payload: A claim that contains user information or other data, such as a user ID, name, and email address. The statement. Can be divided into three types: registered, public and private

  • Signature: To ensure the authenticity of the JWT, different algorithms can be used

header

The first part of the token, e.g

{
  "alg": "HS256"."typ": "JWT"
}
Copy the code

Base64 encoding of the json above yields the first part of the JWT

payload

The second part of token is as follows

  • Registered claims: predefined statement, usually put some predefined fields, such as the expiration time, theme, etc (iss: issuer, exp: expiration time, sub: subject, aud: on)

  • Public Claims: You can set publicly defined fields

  • Private Claims: Information shared between parties that use them uniformly

Do not place sensitive information in headers and payloads unless the information itself is desensitized. The payload can be obtained using a token

{
  "sub": "1234567890"."name": "John Doe"."admin": true
}
Copy the code

Signature

The third part of token

To get the signature part, you must have an encoded header, payload, and a secret key. The signature algorithm uses the one specified in the header and then signs it

HMACSHA256(base64UrlEncode(header)+”.”+base64UrlEncode(payload),secret)

A signature is used to verify that a message has not been changed during delivery, and, for tokens signed with a private key, it can also validate JWT

Is the sender of the.

Purpose of signature

The final step in the signing process is actually signing the header and payload contents.

In general, encryption algorithms always produce different outputs for different inputs. For two different inputs, the probability of producing the same output is extremely small. Therefore, we regard “different inputs produce different outputs” as a necessary event.

Therefore, if the contents of the header and payload are decoded and modified, then the signature of the new header and payload will be different from the previous signature. And if you don’t know the key the server used to encrypt it, the signature is bound to be different.

Upon receiving the JWT, the server application first re-signs the contents of the header and payload using the same algorithm. So how does the server application know which algorithm we’re using?

Our encryption algorithm is already specified in the ALG field in the JWT header.

If the server application signs the header and payload in the same way again and finds that the signature calculated by the server is different from the signature received, then it indicates that the content of the Token has been tamped by others, and we should reject the Token.

Note: in JWT, you should not add any sensitive data to the payload, such as the user’s password. The specific reasons have been given the answer above

JWT. IO website

At jwt. IO (https://jwt.io/#debugger-io), some JWT token encoding, validation, and JWT generation tools are provided.

The following figure shows the components of a typical JWT-Token.

When to use JWT?

It is important to understand that JWT is used for authentication, not authorization. Knowing its capabilities, the application scenario for JWT is self-evident

  • Authorization: In a typical scenario, the tokens requested by users contain the routes, services, and resources allowed by the tokens. Single sign-on is actually a feature that is now widely used in JWT

  • Information Exchange: JSON Web Tokens are a great way to securely transfer Information between parties. Because JWTs can be signed

    For example, with a public/private key pair, you can be sure that the sender is who they say it is. Also, because the signature is computed using headers and payloads, you can verify that the content has not been tampered with

How does JWT work?

The JWT certification process is basically divided into two stages

  • In the first phase, the client obtains the token from the server
  • In the second stage, the client takes the token to request the related resource

What is often important is how the server generates tokens according to the specified rules.

During authentication, a JSON Web Token is returned when the user successfully logs in with their credentials. After that, tokens are user credentials, and you have to be very careful to avoid security issues. In general, you shouldn’t keep a token longer than you need it.

Whenever a user wants to access a protected route or resource, a user agent (usually a browser) should carry a JWT, typically in an Authorization header, with Bearer Schema: Authorization: The protected routes on Bearer servers will check whether the JWT in the Authorization header is valid, and if so, users can access the protected resources.

If JWT contains enough required data, you can reduce the need for database queries for certain operations, although this may not always be the case. If the token is sent in an Authorization header, cross-source resource sharing (CORS) will not be an issue because it does not use cookies.

To feel an official picture

Get JWT and access APIs and resources

  • The client requests authorization from the authorization interface

  • After authorization, the server returns an Access token to the client

  • Clients use the Access token to access protected resources

3 Token-based authentication and server-based authentication

1. The identity authentication given to the server is usually based on the session on the server for user authentication. Using the session may cause the following problems

  • Sessions: After the authentication succeeds, the session data of the user is saved in the memory. As the number of authenticated users increases, the memory cost increases
  • Scalability issues: Since sessions are stored in memory, scalability is limited, although redis and memcached can be used to cache data in the future
  • CORS: When multiple terminals access the same piece of data, they may encounter the problem of denying requests
  • CSRF: Users are vulnerable to Cross Site Request Forgery.

Token-based authentication is stateless. No user information is stored in the server or session.

  • The user requests to obtain the token with the user name and password (the interface data can use appId,appKey, or some kind of data negotiated by himself)

  • The server verifies the user’s credentials and returns a Token to the user or client

  • The client stores the token and carries it in the request header

  • The server verifies the token and returns the corresponding data

A few points to note:

  • When a client requests a server, it must put the token in the header
  • Each time the client requests the server, it needs to carry the token
  • The server needs to be set up to receive requests from all domains:Access-Control-Allow-Origin: *

3. What is the difference between Session and JWT Token?

  • Both can store user-related information
  • The session is stored on the server and the JWT is stored on the client

4 What are the advantages of using Token?

  • He is astatelessandGood scalability
  • It is relatively secure: protection against CSRF attacks, token expiration re-authentication

As mentioned above, JWT is used for identity authentication rather than authorization, so here is a list of where authentication and authorization are used respectively.

  • For example,OAuth2Is an authorization framework for authorization, mainly used in the case of using a third-party account to log in (such as using Weibo, QQ, github to log in an APP).
  • JWT is an authentication protocol that is used when the back-end API needs to be easily protected
  • For both authorization and authentication, remember to use HTTPS to protect data security

5 Actually see how JWT does authentication

  • How to generate tokens based on header, payload, signature
  • How to verify the request made by a client with a token?

The following example code, mainly do two interfaces

Technical points used:

  • gin
    • Routing group
    • Use of middleware
  • gorm
    • Simple operation of mysql database, insert, query
  • jwt
    • To generate the token
    • Parsing the token

Login interface

To access the url: http://127.0.0.1:9999/v1/login

Function:

  • The user login
  • Generate the JWT and return it to the client
  • Gorm operations on the database

Hello interface after authentication

To access the url: http://127.0.0.1:9999/v1/auth/hello

Function:

  • The verification client requests the server to carry a token
  • Returns the data requested by the client

The code structure is shown below

main.go

package main

import (
   "github.com/gin-gonic/gin"
   "my/controller"
   "my/myauth"
)

func main(a) {
   // Connect to the database
   conErr := controller.InitMySQLCon()
   ifconErr ! =nil {
      panic(conErr)
   }

   // You need to use GORm, so you need to do an initialization
   controller.InitModel()
   defer controller.DB.Close()


   route := gin.Default()

   // Route grouping
   v1 := route.Group("/v1/")
   {
      // Login (the registration and login functions are written together for convenience)
      v1.POST("/login", controller.Login)
   }


   v2 := route.Group("/v1/auth/")
   // An authentication middleware
   v2.Use(myauth.JWTAuth())
   {
      // Request server with token
      v2.POST("/hello", controller.Hello)
   }

   // Listen on port 9999
   route.Run(": 9999")}Copy the code

controller.go

The basic data structure in the file is defined as:

The handlers involved in the file:

Actual source code:

package controller

import (
   "errors"
   "fmt"
   jwtgo "github.com/dgrijalva/jwt-go"
   "github.com/gin-gonic/gin"
   "github.com/jinzhu/gorm"
   "log"
   "net/http"
   "time"
   _ "github.com/jinzhu/gorm/dialects/mysql"
)

// Login request information
type ReqInfo struct {
   Name   string `json:"name"`
   Passwd string `json:"passwd"`
}

// Construct the user table
type MyInfo struct {
   Id        int32  `gorm:"AUTO_INCREMENT"`
   Name      string `json:"name"`
   Passwd    string `json:"passwd"`
   CreatedAt *time.Time
   UpdateTAt *time.Time
}

//Myclaims
// Define the payload
type Myclaims struct {
   Name string `json:"userName"`
   // StandardClaims implements Claims interface (Valid() function)
   jwtgo.StandardClaims
}
/ / key
type JWT struct {
   SigningKey []byte
}
/ / hello interface
func Hello(c *gin.Context) {
   claims, _ := c.MustGet("claims").(*Myclaims)
   ifclaims ! =nil {
      c.JSON(http.StatusOK, gin.H{
         "status": 0."msg":    "Hello wrold"."data":   claims,
      })
   }
}

var (
   DB               *gorm.DB
   secret                 = "iamsecret"
   TokenExpired     error = errors.New("Token is expired")
   TokenNotValidYet error = errors.New("Token not active yet")
   TokenMalformed   error = errors.New("That's not even a token")
   TokenInvalid     error = errors.New("Couldn't handle this token:"))// Database connection
func InitMySQLCon(a) (err error) {
   // Can be set to init function in API package
   connStr := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s? charset=utf8mb4&parseTime=True&loc=Local"."root"."123456"."127.0.0.1".3306."mygorm")
   fmt.Println(connStr)
   DB, err = gorm.Open("mysql", connStr)

   iferr ! =nil {
      return err
   }

   return DB.DB().Ping()
}
// Initialize the GORM object map
func InitModel(a) {
   DB.AutoMigrate(&MyInfo{})
}
func NewJWT(a) *JWT {
   return &JWT{
      []byte(secret),
   }
}
// Login result
type LoginResult struct {
   Token string `json:"token"`
   Name string `json:"name"`
}
// Create tokens (claims based on user's basic information)
// Use the HS256 algorithm to generate tokens
// Generates a token using a user's basic information claims and a signkey
func (j *JWT) CreateToken(claims Myclaims) (string, error) {
   // Returns a token structure pointer
   token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS256, claims)
   return token.SignedString(j.SigningKey)
}
/ / token is generated
func generateToken(c *gin.Context, info ReqInfo) {
   // Construct SignKey: signature and unsignature require a value
   j := NewJWT()

   // Construct user claims information (load)
   claims := Myclaims{
      info.Name,
      jwtgo.StandardClaims{
         NotBefore: int64(time.Now().Unix() - 1000), // Signature validity time
         ExpiresAt: int64(time.Now().Unix() + 3600), // Signature expiration time
         Issuer:    "pangsir".// Signature issuer}},// Generate token objects based on claims
   token, err := j.CreateToken(claims)
   iferr ! =nil {
      c.JSON(http.StatusOK, gin.H{
         "status": - 1."msg":    err.Error(),
         "data":   nil,
      })
   }

   log.Println(token)
   // Return user data
   data := LoginResult{
      Name:  info.Name,
      Token: token,
   }

   c.JSON(http.StatusOK, gin.H{
      "status": 0."msg":    "Successful landing."."data":   data,
   })

   return
}
//解析token
func (j *JWT) ParserToken(tokenstr string) (*Myclaims, error) {
   / / input token
   // Output a custom function to parse the token string as a pointer to the token structure of JWT
   // Keyfunc is an anonymous function type: type Keyfunc func(*Token) (interface{}, error)
   // func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {}
   token, err := jwtgo.ParseWithClaims(tokenstr, &Myclaims{}, func(token *jwtgo.Token) (interface{}, error) {
      return j.SigningKey, nil
   })

   fmt.Println(token, err)
   iferr ! =nil {
      // jwt.ValidationError is an error structure for invalid tokens
      if ve, ok := err.(*jwtgo.ValidationError); ok {
         // ValidationErrorMalformed is a uint constant, indicating that the token is unavailable
         ifve.Errors&jwtgo.ValidationErrorMalformed ! =0 {
            return nil, TokenMalformed
            // ValidationErrorExpired Indicates that the Token has expired
         } else ifve.Errors&jwtgo.ValidationErrorExpired ! =0 {
            return nil, TokenExpired
            / / ValidationErrorNotValidYet said invalid token
         } else ifve.Errors&jwtgo.ValidationErrorNotValidYet ! =0 {
            return nil, TokenNotValidYet
         } else {
            return nil, TokenInvalid
         }

      }
   }

   // The claims information in the token is parsed and verified against the original user data
   Convert token.Claims to a specific user-defined Claims structure by making the following type assertions
   if claims, ok := token.Claims.(*Myclaims); ok && token.Valid {
      return claims, nil
   }

   return nil, errors.New("token NotValid")}/ / login
func Login(c *gin.Context) {
   var reqinfo ReqInfo
   var userInfo MyInfo

   err := c.BindJSON(&reqinfo)

   if err == nil {
      fmt.Println(reqinfo)

      if reqinfo.Name == "" || reqinfo.Passwd == ""{
         c.JSON(http.StatusOK, gin.H{
            "status": - 1."msg":    "Account password cannot be empty."."data":   nil,
         })
         c.Abort()
         return
      }
      // Verify whether the user exists in the database
      err := DB.Where("name = ?", reqinfo.Name).Find(&userInfo)
      iferr ! =nil {
         fmt.Println("This user is not in the database, you can add user data")
         // Add users to the database
         info := MyInfo{
            Name:   reqinfo.Name,
            Passwd: reqinfo.Passwd,
         }
         dberr := DB.Model(&MyInfo{}).Create(&info).Error
         ifdberr ! =nil {
            c.JSON(http.StatusOK, gin.H{
               "status": - 1."msg":    "Login failed, database operation error"."data":   nil,
            })
            c.Abort()
            return}}else{
         ifuserInfo.Name ! = reqinfo.Name || userInfo.Passwd ! = reqinfo.Passwd{ c.JSON(http.StatusOK, gin.H{"status": - 1."msg":    "Incorrect account password"."data":   nil,
            })
            c.Abort()
            return}}/ / create a token
      generateToken(c, reqinfo)
   } else {
      c.JSON(http.StatusOK, gin.H{
         "status": - 1."msg":    "Login failed, data request error"."data":   nil,}}}Copy the code

myauth.go

package myauth

import (
   "fmt"
   "github.com/gin-gonic/gin"
   "my/controller"
   "net/http"
)

// Identity authentication
func JWTAuth(a) gin.HandlerFunc {
   return func(c *gin.Context) {
      / / get the token
      token := c.Request.Header.Get("token")
      if token == "" {
         c.JSON(http.StatusOK, gin.H{
            "status": - 1."msg":    "Token is empty. Please carry token."."data":   nil,
         })
         c.Abort()
         return
      }

      fmt.Println("token = ", token)

      // Parse out the actual load
      j := controller.NewJWT()

      claims, err := j.ParserToken(token)
      iferr ! =nil {
         / / token expired
         if err == controller.TokenExpired {
            c.JSON(http.StatusOK, gin.H{
               "status": - 1."msg":    "Token authorization has expired, please reapply for authorization"."data":   nil,
            })
            c.Abort()
            return
         }
         // Other errors
         c.JSON(http.StatusOK, gin.H{
            "status": - 1."msg":    err.Error(),
            "data":   nil,
         })
         c.Abort()
         return
      }

      // Resolve to specific claims related information
      c.Set("claims", claims)
   }
}
Copy the code

myauth.go

package myauth

import (
   "fmt"
   "github.com/gin-gonic/gin"
   "my/controller"
   "net/http"
)

// Identity authentication
func JWTAuth(a) gin.HandlerFunc {
   return func(c *gin.Context) {
      / / get the token
      token := c.Request.Header.Get("token")
      if token == "" {
         c.JSON(http.StatusOK, gin.H{
            "status": - 1."msg":    "Token is empty. Please carry token."."data":   nil,
         })
         c.Abort()
         return
      }

      fmt.Println("token = ", token)

      // Parse out the actual load
      j := controller.NewJWT()

      claims, err := j.ParserToken(token)
      iferr ! =nil {
         / / token expired
         if err == controller.TokenExpired {
            c.JSON(http.StatusOK, gin.H{
               "status": - 1."msg":    "Token authorization has expired, please reapply for authorization"."data":   nil,
            })
            c.Abort()
            return
         }
         // Other errors
         c.JSON(http.StatusOK, gin.H{
            "status": - 1."msg":    err.Error(),
            "data":   nil,
         })
         c.Abort()
         return
      }

      // Resolve to specific claims related information
      c.Set("claims", claims)
   }
}
Copy the code

6 How does JWT assemble Header, Paylaod, and Signature?

1> Let’s start with the function that creates the token

CreateToken is bound with a JWT object that contains the key and the function’s parameters are payloads

2> NewWithClaims function argument is the encryption algorithm, payload

NewWithClaims initializes a Token object

3> SignedString function, parameter is the key

Basically get a full token

SigningString concatenates header with payload processing

Sign computes a hash value for the key, splices it together with the header and payload, and then makes a token

Read on to see which implementation the Sign method calls

4> SigningString

Use Base64 encryption after serializing the header through JSON

Likewise, the payload is encrypted using Base64 after being serialized through JSON

Concatenate the two encrypted strings together

5> Return to the position where the token function was created

func (j *JWT) CreateToken(claims Myclaims) (string, error) {
   // Returns a token structure pointer
   token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS256, claims)
   return token.SignedString(j.SigningKey)
}
Copy the code

SigningMethodHS256 corresponds to this structure SigningMethodHMAC, as follows

Seeing this, I solve the question of the above point 4: where is the Sign method implemented

7> Effect view

Login & Register interface

Database display (if you have questions about gorM in the code, you can see the collation of Nezha’s last GORM)

Hello interface

The above is all the content of this issue. If you have any questions, you can put forward your questions in the comments section or backstage. We will communicate and grow together.

Good guy if the article is still useful to you, please help point a concern, share to your circle of friends, share technology, share happiness

Technology is open, our mentality, should be more open. Embrace change, live in the sun, and strive to move forward.

Author: Nezha