參考文檔
// 文檔
https://github.com/golang-jwt/jwt
https://pkg.go.dev/github.com/golang-jwt/jwt@v3.2.2+incompatible#example-NewWithClaims-CustomClaimsType
https://gin-gonic.com/zh-cn/docs/examples/using-middleware/
https://gin-gonic.com/zh-cn/docs/examples/custom-middleware/
下載
// 下載
go get -u github.com/golang-jwt/jwt
實戰
// util/jwt.go
package util
import (
"gindemo/pkg/setting"
"time"
"github.com/golang-jwt/jwt"
)
var jwtSecret = []byte(setting.JwtSecret) //配置文件中自己配置的
// Claims是一些用戶信息狀態和額外的jwt參數
type Claims struct {
Username string `json:"username"`
Password string `json:"password"`
jwt.StandardClaims
}
// 根據用戶的用戶名和密碼參數token
func GenerateToken(username, password string) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(time.Minute * 15).Unix()
claims := Claims{
Username: username,
Password: password,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime, // 過期時間
Issuer: "gindemo", //指定發行人
},
}
// 該方法內部生成簽名字符串,再用於獲取完整、已簽名的token
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(jwtSecret)
return token, err
}
// 根據傳入的token值獲取到Claims對象信息(進而獲取其中的用戶名和密碼)
func ParseToken(token string) (*Claims, error) {
// 用於解析鑒權的聲明,方法內部主要是具體的解碼和校驗的過程,最終返回*Token
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if tokenClaims != nil {
// 從tokenClaims中獲取到Claims對象,並使用斷言,將該對象轉換為我們自己定義的Claims
// 要傳入指針,項目結構體都是用指針傳遞,節省空間
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { // Valid()驗證基於時間的聲明
return claims, nil
}
}
return nil, err
}
// 中間件
// middleware/jwt/jwt.go
package jwt
import (
"gindemo/pkg/e"
"gindemo/pkg/util"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// 自定義中間件
func JWT() gin.HandlerFunc {
return func(c *gin.Context) {
var code int
var data interface{}
code = e.SUCCESS
token := c.Query("token")
if token == "" {
code = e.INVALID_PARAMS
} else {
// 解析token
claims, err := util.ParseToken(token)
if err != nil {
code = e.ERROR_AUTH_CHECK_TOKEN_FAIL
} else if time.Now().Unix() > claims.ExpiresAt {
code = e.ERROR_AUTH_CHECK_TOKEN_TIMEOUT
}
}
if code != e.SUCCESS {
c.JSON(http.StatusUnauthorized, gin.H{
"code": code,
"msg": e.GetMsg(code),
"data": data,
})
c.Abort()
return
}
c.Next()
}
}
// models/auth.go
// 數據庫查用戶
package models
type Auth struct {
ID int `gorm:"primary_key" json:"id"`
Username string `json:"username"`
Password string `json:"password"`
}
func CheckAuth(username, password string) bool {
var auth Auth
db.Select("id").Where(Auth{Username: username, Password: password}).First(&auth)
return auth.ID > 0
}
// routers/api/auth.go
// 用戶認證邏輯視圖
package api
import (
"gindemo/models"
"gindemo/pkg/e"
"gindemo/pkg/util"
"log"
"net/http"
"github.com/astaxie/beego/validation"
"github.com/gin-gonic/gin"
)
type auth struct {
Username string `valid:"Required;MaxSize(50)"`
Password string `valid:"Required;MaxSize(50)"`
}
func GetAuth(c *gin.Context) {
username := c.Query("username")
password := c.Query("password")
valid := validation.Validation{}
a := auth{Username: username, Password: password}
ok, _ := valid.Valid(&a)
data := make(map[string]interface{})
code := e.INVALID_PARAMS
if ok {
// 去數據庫中查詢用戶是否存在
isExist := models.CheckAuth(username, password)
if isExist {
// 創建token
token, err := util.GenerateToken(username, password)
if err != nil {
code = e.ERROR_AUTH_TOKEN
} else {
data["token"] = token
code = e.SUCCESS
}
} else {
code = e.ERROR_AUTH
}
} else {
for _, err := range valid.Errors {
log.Println(err.Key, err.Message)
}
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": e.GetMsg(code),
"data": data,
})
}
// routers/router.go
// 添加auth路由
package routers
import (
"gindemo/middleware/jwt"
"gindemo/pkg/setting"
"gindemo/routers/api"
v1 "gindemo/routers/api/v1"
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
gin.SetMode(setting.RunMode)
// 認證
r.GET("/auth", api.GetAuth)
// 路由組
apiv1 := r.Group("/api/v1")
{
...
}
return r
}
// 測試
http://127.0.0.1:8000/auth?username=test&password=test123456
// 返回數據
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6InRlc3QxMjM0NTYiLCJleHAiOjE2NDYzMzc0NzMsImlzcyI6ImdpbmRlbW8ifQ.mFGXb6dyYFGIni3joNinfpsNmeDAvvDOFKSfvJ4ss1w"
},
"msg": "ok"
}
// 將中間件接入Gin
// routers/router.go
package routers
import (
"gindemo/middleware/jwt"
"gindemo/pkg/setting"
"gindemo/routers/api"
v1 "gindemo/routers/api/v1"
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
gin.SetMode(setting.RunMode)
// 認證
r.GET("/auth", api.GetAuth)
// 路由組
apiv1 := r.Group("/api/v1")
// 使用中間件認證
apiv1.Use(jwt.JWT())
{
...
}
return r
}
// 以后每次請求的時候先獲取Token,然后請求其他url的時候帶上token就可以了