当前位置:网站首页>[goweb development] Introduction to authentication modes based on cookies, sessions and JWT tokens

[goweb development] Introduction to authentication modes based on cookies, sessions and JWT tokens

2022-07-05 04:34:00 Hu Maomao_ March

User authentication


HTTP Is a stateless protocol , After a request , Next time, the sending server will not know who sent the request ( The same IP Does not represent the same user ), stay Web Application , User authentication and authentication are very important , There are many options available in practice , And each has its own merits .

Cookie- Session Authentication mode

stay Web The early days of application development , Most of them are based on Cookie-Session Session management mode , The logic is as follows .

  • The client uses the user name 、 Password Authentication

  • Server authentication user name 、 After the password is correct, it is generated and stored Session, take SessionID adopt Cookie Return to the client

  • When the client accesses the interface that needs authentication, it is in Cookie Middle carry SessionlD

  • Server through SessionID lookup Session And authenticate , Return to the data required by the client

img

be based on Session There are many problems with the way .

  • The server needs to store Session, And because of Session You need to find it often and quickly , Usually stored in memory or memory database , At the same time, when there are many online users, it needs to occupy a lot of server resources .

  • When you need to expand , establish Session Your server may not be an authentication server Session Server for , So you need to put all Session Store and share separately .

  • Because the client uses Cookie Storage SessionlD, Compatibility processing is required in cross domain scenarios , At the same time, this way is also difficult to prevent CSRF attack .


Token Authentication mode

In view of Session The session management mode of has the above disadvantages , be based on Token Stateless session management was born , So called stateless , That is, the server can no longer store information , Even no longer store Session, The logic is as follows .

  • The client uses the user name 、 Password Authentication

  • Server authentication user name 、 Generate after the password is correct Token Return to the client

  • Client save Token, When accessing an interface requiring authentication URL Parameter or HTTP Header Add Token

  • The server decodes Token To authorize , Return to the data required by the client

img


JWT Introduce

JWT yes JSON Web Token Abbreviation , Is a kind of implementation based on the JSON Open standards for ((RFC7519).JWT There is no technical implementation defined by itself , It just defines a method based on Token The rules of session management , cover Token The standard content to be included and Token Generation process of , Especially for single sign in of distributed sites (SSO) scene .

One JWT Token Just like this. :

eyJhbGcioiJIUzI1NiIsInR5cCI6IkpxVCJ9
.eyJ1c2VyX2lkIjoyODAxODcyNzQ4ODMyMZU4NSwiZXhwIjoxNTkONTQwMjkxLCJpc3MiOiJibHVlYmVsbCJ9
.lk_ZrAtYGCeZhK3iupHxP1kgjBTzQTVTtX0izYFx9wU

It is from . Separated by three parts , The three parts are :

  • Head (Header)

  • load (Payload)

  • Signature (Signature)

Head and load in JSON The form , This is it. JWT Medium JSON, The contents of the three parts have been separately Base64 code , With . To join together into one JWT Token.

img

Header

JWT Of Header The encryption algorithm and Token type .

{
    
	"alg": "HS256",
  "TYP": "jwt"
}

Payload

Payload Indicates the load ( take Token As a carrier , Express Token What's inside ), Also a JSON object ,JWT Specifies the 7 There are two official fields to choose from ,

iss (issuer)︰ Issued by people 
exp ( expiration time): Expiration time 
sub ( subject)︰ The theme 
aud (audience)︰ Audience 
nbf (Not Before): entry-into-force time 
iat ( Issued At)︰ The issuance of time 
jti(JwT ID)∶ Number 

Except for official fields , Developers can also specify their own fields and contents , For example, the following .

{
    
  "sub" : "1234567890",
  "name " : "John Doe" ,
  "admin " : true
}

Be careful ,JWT It is not encrypted by default , Anyone can read it , So don't put secret information in this part . This JSON Objects also use Base64URL Algorithm to string .

Signature

Signature Part is the signature of the first two parts , Prevent data tampering .

First , A key needs to be specified (secret). This key is only known to the server , Do not disclose to users . then , Use Header The signature algorithm specified in ( The default is HMAC SHA256), Follow the formula below to generate a signature .

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

JWT Advantages and disadvantages

JWT Based on Token All the advantages of session management , Do not rely on Cookie, So that it can prevent CSRF attack , You can also disable Cookie Normal operation in the browser environment .

and JWT The biggest advantage of is that the server no longer needs storage Session, So that the server authentication business can be easily expanded , Avoid storage Session What needs to be introduced Redis And so on , It reduces the complexity of system architecture . But it's also JWT The biggest disadvantage , Because the validity period is stored in Token in ,JWTToken Once issued , Will remain available for the duration of the validity period , Cannot be abolished on the server , When the user logs out , You can only rely on the client to delete the locally stored JWT Token, Disable the user if necessary , Just use JWT You can't do it .

be based on jwt Implement certification practices

As mentioned earlier Token, All are Access Token, That is, what is needed to access the resource interface Token, There's another one

Token,Refresh Token, Usually ,Refresh Token Will be valid for a long time , and Access Token The validity period of , When Access Token Failure due to expiration , Use Refresh Token You can get new Access Token If Refresh Token It's not working , The user can only log in again .

stay JWT In the practice of , introduce Refresh Token, Improve the session management process as follows .

  • The client uses the user name and password for authentication

  • The server generates a message with a shorter effective time Access Token( for example 10 minute ), And longer effective time RefreshToken( for example 7 God )

  • When the client accesses the interface that needs authentication , carry Access Token

  • If Access Token No expired , After authentication, the server returns the required data to the client

  • If carrying Access Token Authentication failed when accessing the interface requiring authentication ( Such as return 401 error ), Then the client uses Refresh Token Apply to the refresh interface for a new Access Token

  • If Refresh Token No expired , The server sends a new message to the client Access Token The client uses the new Access Token Access the interface that requires authentication

img

The backend needs to provide a refresh Token The interface of , The front end needs to implement a when Access Token Automatically request refresh when it expires Token Interface got

Get new Access Token Ballast of .

gin Frame usage jwt

jwt-go See : stay gin Use... In the framework JWT

package jwt

import (
	"errors"
	"time"

	"github.com/dgrijalva/jwt-go"
)

// MyClaims  Customize the declaration structure and embed jwt.StandardClaims
// jwt It comes with you jwt.StandardClaims Only official fields are included 
//  We need to record an additional UserID Field , So you need to customize the structure 
//  If you want to save more information , Can be added to this structure 
type MyClaims struct {
    
	UserID uint64 `json:"user_id"`
	Username string `json:"username"`
	jwt.StandardClaims
}
// Definition Secret
var mySecret = []byte(" Summer and summer slip by ")

func keyFunc(_ *jwt.Token) (i interface{
    }, err error) {
    
	return mySecret, nil
}

// Definition JWT The expiration time of 
const TokenExpireDuration = time.Hour * 2

/** * @Author huchao * @Description //TODO  Generate JWT * @Date 9:42 2022/2/11 **/
// GenToken  Generate access token  and  refresh token
func GenToken(userID uint64,username string) (aToken, rToken string, err error) {
    
	//  Create our own statement 
	c := MyClaims{
    
		userID, //  Custom field 
		"username",	//  Custom field 
		jwt.StandardClaims{
    	// JWT Stipulated 7 Official fields 
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), //  Expiration time 
			Issuer:    "bluebell",                                 //  Issued by people 
		},
	}
	//  Encrypt and get the complete encoded string token
	aToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, c).SignedString(mySecret)

	// refresh token  There is no need to save any custom data 
	rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
    
		ExpiresAt: time.Now().Add(time.Second * 30).Unix(), //  Expiration time 
		Issuer:    "bluebell",                              //  Issued by people 
	}).SignedString(mySecret)
	//  Use specified secret Sign and get the complete encoded string token
	return
}
//GenToken  Generate  Token
func GenToken2(userID uint64, username string) (Token string, err error) {
    
	//  Create our own statement 
	c := MyClaims{
    
		userID, 			//  Custom field 
		"username",	//  Custom field 
		jwt.StandardClaims{
    	// JWT Stipulated 7 Official fields 
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), //  Expiration time 
			Issuer:    "bluebell",                                 //  Issued by people 
		},
	}
	//  Encrypt and get the complete encoded string token
	Token, err = jwt.NewWithClaims(jwt.SigningMethodHS256, c).SignedString(mySecret)

	// refresh token  There is no need to save any custom data 
	//rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
    
	// ExpiresAt: time.Now().Add(time.Second * 30).Unix(), //  Expiration time 
	// Issuer: "bluebell", //  Issued by people 
	//}).SignedString(mySecret) //  Use specified secret Sign and get the complete encoded string token
	return
}

/** * @Author huchao * @Description //TODO  analysis JWT * @Date 9:43 2022/2/11 **/
func ParseToken(tokenString string) (claims *MyClaims, err error) {
    
	//  analysis token
	var token *jwt.Token
	claims = new(MyClaims)
	token, err = jwt.ParseWithClaims(tokenString, claims, keyFunc)
	if err != nil {
    
		return
	}
	if !token.Valid {
     //  check token
		err = errors.New("invalid token")
	}
	return
}

// RefreshToken  Refresh AccessToken
func RefreshToken(aToken, rToken string) (newAToken, newRToken string, err error) {
    
	// refresh token Invalid direct return 
	if _, err = jwt.Parse(rToken, keyFunc); err != nil {
    
		return
	}

	//  From the old access token Resolve in claims data   It is concluded that payload Load information 
	var claims MyClaims
	_, err = jwt.ParseWithClaims(aToken, &claims, keyFunc)
	v, _ := err.(*jwt.ValidationError)

	//  When access token Is an expiration error   also  refresh token Create a new one before it expires access token
	if v.Errors == jwt.ValidationErrorExpired {
    
		return GenToken(claims.UserID,claims.Username)
	}
	return
}

Authentication middleware development

const (
	ContextUserIDKey = "userID"
)

var (
	ErrorUserNotLogin = errors.New(" The current user is not logged in ")
)

// JWTAuthMiddleware  be based on JWT Authentication middleware for 
func JWTAuthMiddleware() func(c *gin.Context) {
    
	return func(c *gin.Context) {
    
		//  The client carries Token There are three ways  1. Put it on the request  2. Put in request body  3. Put it in URI
		//  It is assumed that Token Put it in Header Of Authorization in , And use Bearer start 
		//  The specific implementation method here should be determined according to your actual business situation 
		authHeader := c.Request.Header.Get("Authorization")
		if authHeader == "" {
    
			controller.ResponseErrorWithMsg(c, controller.CodeInvalidToken, " The request header is missing Auth Token")
			c.Abort()
			return
		}
		//  Split by space 
		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
    
			controller.ResponseErrorWithMsg(c, controller.CodeInvalidToken, "Token Wrong format ")
			c.Abort()
			return
		}
		// parts[1] It's got tokenString, We use the previously defined parsing JWT To parse it 
		mc, err := jwt.ParseToken(parts[1])
		if err != nil {
    
			fmt.Println(err)
			controller.ResponseError(c, controller.CodeInvalidToken)
			c.Abort()
			return
		}
		//  Will the currently requested userID Save the information to the context of the request c On 
		c.Set(.ContextUserIDKey, mc.UserID)
		c.Next() //  Subsequent processing functions can be used c.Get(ContextUserIDKey) To get the currently requested user information 
	}
}

Generate access token and refresh token

// GenToken  Generate access token  and  refresh token 
func GenToken(userID uint64, username string) (Token string, err error) {
    
	//  Create our own statement 
	c := MyClaims{
    
		userID, 			//  Custom field 
		"username",	//  Custom field 
		jwt.StandardClaims{
    	// JWT Stipulated 7 Official fields 
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), //  Expiration time 
			Issuer:    "bluebell",                                 //  Issued by people 
		},
	}
	//  Encrypt and get the complete encoded string token
	Token, err = jwt.NewWithClaims(jwt.SigningMethodHS256, c).SignedString(mySecret)

	// refresh token  There is no need to save any custom data 
	//rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
    
	// ExpiresAt: time.Now().Add(time.Second * 30).Unix(), //  Expiration time 
	// Issuer: "bluebell", //  Issued by people 
	//}).SignedString(mySecret) //  Use specified secret Sign and get the complete encoded string token
	return
}

analysis access token

//  analysis JWT
func ParseToken(tokenString string) (claims *MyClaims, err error) {
    
	//  analysis token
	var token *jwt.Token
	claims = new(MyClaims)
	token, err = jwt.ParseWithClaims(tokenString, claims, keyFunc)
	if err != nil {
    
		return
	}
	if !token.Valid {
     //  check token
		err = errors.New("invalid token")
	}
	return
}

refresh token

// RefreshToken  Refresh AccessToken
func RefreshToken(aToken, rToken string) (newAToken, newRToken string, err error) {
    
	// refresh token Invalid direct return 
	if _, err = jwt.Parse(rToken, keyFunc); err != nil {
    
		return
	}

	//  From the old access token Resolve in claims data 
	var claims MyClaims
	_, err = jwt.ParseWithClaims(aToken, &claims, keyFunc)
	v, _ := err.(*jwt.ValidationError)

	//  When access token Is an expiration error   also  refresh token Create a new one before it expires access token
	if v.Errors == jwt.ValidationErrorExpired {
    
		return GenToken(claims.UserID)
	}
	return
}

Related reference links

  • https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
原网站

版权声明
本文为[Hu Maomao_ March]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202140635568322.html