preface

Since JWT is stateless, it can only become invalid when its validity period expires. The server cannot actively invalidate a token. To solve this problem, I use a blacklist strategy to solve the cancellation problem of JWT. The validity period must be set; otherwise, the blacklist will cause huge problems. Then, the Jwt middleware checks whether the token is in the blacklist during authentication

The installation

go get -u github.com/go-redis/redis/v8
Copy the code

Defining configuration items

Create the config/redis.go file and write the configuration

package config

type Redis struct {
    Host string `mapstructure:"host" json:"host" yaml:"host"`
    Port int `mapstructure:"port" json:"port" yaml:"port"`
    DB int `mapstructure:"db" json:"db" yaml:"db"`
    Password string `mapstructure:"password" json:"password" yaml:"password"`
}
Copy the code

In config/config.go, add the Redis attribute

package config

type Configuration struct {
    App App `mapstructure:"app" json:"app" yaml:"app"`
    Log Log `mapstructure:"log" json:"log" yaml:"log"`
    Database Database `mapstructure:"database" json:"database" yaml:"database"`
    Jwt Jwt `mapstructure:"jwt" json:"jwt" yaml:"jwt"`
    Redis Redis `mapstructure:"redis" json:"redis" yaml:"redis"`
}
Copy the code

Add the JwtBlacklistGracePeriod property in config/jwt.go

package config

type Jwt struct {
    Secret string `mapstructure:"secret" json:"secret" yaml:"secret"`
    JwtTtl int64 `mapstructure:"jwt_ttl" json:"jwt_ttl" yaml:"jwt_ttl"` // Token validity period (seconds)
    JwtBlacklistGracePeriod int64 `mapstructure:"jwt_blacklist_grace_period" json:"jwt_blacklist_grace_period" yaml:"jwt_blacklist_grace_period"` // Blacklist grace time (seconds)
}
Copy the code

Config. yaml Adds the corresponding configuration

redis:
  host: 127.0. 01.
  port: 6379
  db: 0
  password:

jwt:
  jwt_blacklist_grace_period: 10
Copy the code

Initialize the Redis

Create the bootstrap/redis.go file and write

package bootstrap

import (
    "context"
    "github.com/go-redis/redis/v8"
    "go.uber.org/zap"
    "jassue-gin/global"
)

func InitializeRedis(a) *redis.Client {
    client := redis.NewClient(&redis.Options{
        Addr:     global.App.Config.Redis.Host + ":" + global.App.Config.Redis.Port,
        Password: global.App.Config.Redis.Password, // no password set
        DB:       global.App.Config.Redis.DB,       // use default DB
    })
    _, err := client.Ping(context.Background()).Result()
    iferr ! =nil {
        global.App.Log.Error("Redis connect ping failed, err:", zap.Any("err", err))
        return nil
    }
    return client
}
Copy the code

In global/app.go, the Application structure adds the Redis attribute

type Application struct {
    // ...
    Redis *redis.Client
}
Copy the code

In main.go, call InitializeRedis()

func main(a) {
    // ...

    // Initializes the validator
    bootstrap.InitializeValidator()

    // Initialize Redis
    global.App.Redis = bootstrap.InitializeRedis()

    // Start the server
    bootstrap.RunServer()
}
Copy the code

Compile blacklist logic

Create the utils/md5.go file and write md5 () for token encoding

package utils

import (
    "crypto/md5"
    "encoding/hex"
)

func MD5(str []byte, b ...byte) string {
    h := md5.New()
    h.Write(str)
    return hex.EncodeToString(h.Sum(b))
}
Copy the code

In app/services/jwt.go, write:

// Obtain the blacklist cache key
func (jwtService *jwtService) getBlackListKey(tokenStr string) string {
    return "jwt_black_list:" + utils.MD5([]byte(tokenStr))
}

// JoinBlackList Token joins the blacklist
func (jwtService *jwtService) JoinBlackList(token *jwt.Token) (err error) {
    nowUnix := time.Now().Unix()
    timer := time.Duration(token.Claims.(*CustomClaims).ExpiresAt - nowUnix) * time.Second
    // Set the token remaining time to the cache validity period and use the current time as the cache value
    err = global.App.Redis.SetNX(context.Background(), jwtService.getBlackListKey(token.Raw), nowUnix, timer).Err()
    return
}

// IsInBlacklist Token is in the blacklist
func (jwtService *jwtService) IsInBlacklist(tokenStr string) bool {
    joinUnixStr, err := global.App.Redis.Get(context.Background(), jwtService.getBlackListKey(tokenStr)).Result()
    joinUnix, err := strconv.ParseInt(joinUnixStr, 10.64)
    if joinUnixStr == ""|| err ! =nil {
        return false
    }
    // JwtBlacklistGracePeriod Specifies the grace period of the blacklist to prevent concurrent requests from becoming invalid
    if time.Now().Unix()-joinUnix < global.App.Config.Jwt.JwtBlacklistGracePeriod {
        return false
    }
    return true
}
Copy the code

Add blackcheck in app/ Middleware /jwt.go

func JWTAuth(GuardName string) gin.HandlerFunc {
    return func(c *gin.Context) {
        // ...
        token, err := jwt.ParseWithClaims(tokenStr, &services.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
            return []byte(global.App.Config.Jwt.Secret), nil
        })
        iferr ! =nil || services.JwtService.IsInBlacklist(tokenStr) {
            response.TokenFail(c)
            c.Abort()
            return
        }

        claims := token.Claims.(*services.CustomClaims)
        // ...}}Copy the code

Implement the logout interface

Add the routing

func SetApiGroupRoutes(router *gin.RouterGroup) {
    // ...
    authRouter := router.Group("").Use(middleware.JWTAuth(app.GuardName))
    {
        authRouter.POST("/auth/info", app.Info)
        authRouter.POST("/auth/logout", app.Logout)
    }
}
Copy the code

In the app/controllers/app/auth. Go, write

func Logout(c *gin.Context) {
    err := services.JwtService.JoinBlackList(c.Keys["token"].(*jwt.Token))
    iferr ! =nil {
        response.BusinessFail(c, "Logout failed")
        return
    }
    response.Success(c, nil)}Copy the code

test

Invoke the login interface to obtain the token

Will join the Authorization header token, call log out interface at http://localhost:8888/api/auth/logout

After the JwtBlacklistGracePeriod blacklist grace period expires, further calls to the logout interface will not respond successfully