初始化
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数据库
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) {
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, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "failed to hash password",
})
}
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
}
c.JSON(http.StatusOK, gin.H{})
}
func Login(c *gin.Context) {
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
}
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"
"gorm.io/gorm"
)
var DB *gorm.DB
func ConnectToDb() {
var err error
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
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")
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) {
tokenStr, err := c.Cookie("Authorization")
if err != nil {
c.AbortWithStatus(http.StatusUnauthorized)
}
fmt.Println(tokenStr)
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)
}
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() {
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中间件请求该认证服务器进行认证
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")
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}
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()
}
}