jwt-go 认证服务器

初始化

1682754591806
1682754628679

go get -u github.com/golang-jwt/jwt/v4
go get gorm.io/gorm
go get gorm.io/driver/sqlite
go get github.com/joho/godotenv
go get github.com/githubnemo/CompileDaemon
go install github.com/githubnemo/CompileDaemon
go get -u golang.org/x/crypto/bcrypt 
# 启动(command="gomod模块名称"),然后可以热更新
compiledaemon --command="./jwt-go-system" 

可以注册使用免费的postgres数据库

1682755461947

1682772056451

controllers

package controllers

import (
    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v4"
    "golang.org/x/crypto/bcrypt"
    "jwt-go-system/initializers"
    "jwt-go-system/models"
    "net/http"
    "os"
    "time"
)

func Signup(c *gin.Context) {
    // 传过来的json里的变量名大小写开头都行
    var body struct {
        Email    string
        Password string
    }
    if c.Bind(&body) != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "failed to read body",
        })
        return
    }
    // hash the password
    hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "failed to hash password",
        })
    }
    // create a new user
    user := models.User{Email: body.Email, Password: string(hash)}
    result := initializers.DB.Create(&user)
    if result.Error != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "failed to create user",
        })
        return
    }
    // response
    c.JSON(http.StatusOK, gin.H{})
}
func Login(c *gin.Context) {
    // 传过来的json里的变量名大小写开头都行
    var body struct {
        Email    string
        Password string
    }
    if c.Bind(&body) != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "failed to read body",
        })
        return
    }
    // 是否有该用户
    var user models.User
    initializers.DB.First(&user, "email = ?", body.Email)
    if user.ID == 0 {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "Invalid email or password",
        })
        return
    }
    // 校验密码
    err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(body.Password))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "failed to hash password",
        })
        return
    }
    // 生成jwt令牌(传输协议用https,加密传输,防止jwt泄露)
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "sub": user.ID,
        // 过期时间
        "exp": time.Now().Add(time.Hour * 24 * 30).Unix(),
    })
    // 传入密钥,加密
    tokenStr, err := token.SignedString([]byte(os.Getenv("SECRET")))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "Failed to create token",
        })
        return
    }
    c.SetSameSite(http.SameSiteLaxMode)
    c.SetCookie("Authorization", tokenStr, 3600*24*30, "", "", false, true)
    c.JSON(http.StatusOK, gin.H{
        "token": tokenStr,
    })
}

func Validate(c *gin.Context) {
    user, _ := c.Get("user")
    c.JSON(http.StatusOK, gin.H{
        "msg":  "ok!",
        "user": user,
    })
}

initializers

connectToDb.go

package initializers

import (
    "gorm.io/driver/sqlite" // 基于 GGO 的 Sqlite 驱动
    // "github.com/glebarez/sqlite" // 纯 Go 实现的 SQLite 驱动, 详情参考: https://github.com/glebarez/sqlite
    "gorm.io/gorm"
)

var DB *gorm.DB

func ConnectToDb() {
    var err error
    // github.com/mattn/go-sqlite3
    DB, err = gorm.Open(sqlite.Open("jwt-go.db"), &gorm.Config{})
    if err != nil {
        panic("fail to connect to database")
    }
}

loadEnvVariables.go

package initializers

import (
    "log"

    "github.com/joho/godotenv"
)

func LoadEnvVariables() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal(err)
    }
}

syncDatabase.go

package initializers

import "jwt-go-system/models"

func SyncDatabase() {
    // 创建表
    DB.AutoMigrate(&models.User{})
}

middleware

cors.go

package middleware

import (
    "net/http"

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

//解决跨域问题
func Core() gin.HandlerFunc {
    return func(c *gin.Context) {
        method := c.Request.Method
        // axios跨域必须origin一致,而不是*
        origin := c.Request.Header.Get("Origin")
        c.Header("Access-Control-Allow-Origin", origin)
        c.Header("Access-Control-Allow-Headers", "*")
        c.Header("Access-Control-Allow-Methods", "*")
        c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type")
        c.Header("Access-Control-Max-Age", "3600")
        c.Header("Access-Control-Allow-Credentials", "true")
        //放行索引options
        if method == "OPTIONS" {
            c.AbortWithStatus(http.StatusNoContent)
        }
        //处理请求
        c.Next()
    }
}

requireAuth.go

package middleware

import (
    "fmt" 
    "jwt-go-system/initializers"
    "jwt-go-system/models"
    "net/http"
    "os"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v4"
)

func RequireAuth(c *gin.Context) {
    // 获取token
    tokenStr, err := c.Cookie("Authorization")
    if err != nil {
        c.AbortWithStatus(http.StatusUnauthorized)
    }
fmt.Println(tokenStr)
    // 验证token
    token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
        }
        return []byte(os.Getenv("SECRET")), nil
    })
    if cliams, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
        // 过期
        if float64(time.Now().Unix()) > cliams["exp"].(float64) {
            c.AbortWithStatus(http.StatusUnauthorized)
        }
        // 根据jwt携带消息(userId)查询数据库
        var user models.User
        initializers.DB.First(&user, cliams["sub"])
        if user.ID == 0 {
            c.AbortWithStatus(http.StatusUnauthorized)
        }
        c.Set("user", user)
        c.Next()
    } else {
        c.AbortWithStatus(http.StatusUnauthorized)
    }
}

models

userModels.go

package models

import "gorm.io/gorm"

type User struct {
    gorm.Model
    Email    string `gorm:"unique"`
    Password string
}

.env

PORT=3000
SECRET=8d6f7b7d-2f24-4867-ae90-feb4fc646693

main.go

package main

import (
    "jwt-go-system/controllers"
    "jwt-go-system/initializers"
    "jwt-go-system/middleware"

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

func init() {
    // 加载env文件里的变量
    initializers.LoadEnvVariables()
    // 链接数据库
    initializers.ConnectToDb()
    // 建表
    initializers.SyncDatabase()
}
func main() {
    r := gin.Default()
    r.Use(middleware.Core())
    r.POST("/signup", middleware.Core(), controllers.Signup)
    r.POST("/login", middleware.Core(), controllers.Login)
    r.GET("/validate", middleware.Core(), middleware.RequireAuth, controllers.Validate)
    r.Run(":3000")
}

gin中间件请求该认证服务器进行认证

// 权限认证(验证token) -> 请求服务器
func TokenValid() gin.HandlerFunc {
    return func(c *gin.Context) {
        url := "http://127.0.0.1:3000/validate"
        req, err := http.NewRequest(http.MethodGet, url, nil)
        if err != nil {
            fmt.Println("TestGetReq http.NewRequest err:", err)
            c.AbortWithStatus(http.StatusUnauthorized)
        }
        token, err := c.Cookie("Authorization")
        // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODUzNjA5NjksInN1YiI6NH0.K2kE3Gjp1OW96XBevTkC6tFPWkskhm02WiCeTGEEOAo
        fmt.Println("token: ", token)
        if err != nil {
            fmt.Println("Get token error:", err)
            c.AbortWithStatus(http.StatusUnauthorized)
        }
        c1 := http.Cookie{
            Name:     "Authorization",
            Value:    token,
            HttpOnly: true,
        }
        req.AddCookie(&c1)
        client := &http.Client{Timeout: 5 * time.Second} // 设置请求超时时长5s
        resp, err := client.Do(req)
        if err != nil {
            fmt.Println("TestGetReq http.DefaultClient.Do() err: ", err)
            c.AbortWithStatus(http.StatusUnauthorized)
        }
        defer resp.Body.Close()
        // 如果认证失败
        if resp.StatusCode == http.StatusUnauthorized {
            c.AbortWithStatus(http.StatusUnauthorized)
        }
        //处理请求
        c.Next()
    }
}

  转载请注明: malred-blog jwt-go 认证服务器

  目录