使用 GoFrame 框架 JWT 方式驗證使用者資訊

994914376發表於2020-01-11

說明

使用說明

官網的 gf-demos 已經包含了基本的示例操作,但是現在都屬於前後端分離開發模式,特對使用者資訊無狀態儲存做了一翻更改,使用jwt生成的token來做使用者狀態識別,接下來進入正題

一、安裝框架與gf-jwt外掛

先安裝框架提供的gf-cli命令列工具 官網地址 執行下面命令

1、  Linux 安裝
    wget https://goframe.org/cli/linux_amd64/gf && chmod +x gf && sudo ./gf install

2、Mac 安裝
    wget https://goframe.org/cli/darwin_amd64/gf && chmod +x gf && ./gf install

更多方式安裝見官網

1、我們使用gf-cli工具來生成我們的專案腳手架

$ cd 你要存放專案的目錄  例:/home/www/goframe
$ gf init <應用名稱> 例 goframe  回車就行
$ gf get  github.com/gogf/gf-jwt  安裝jwt包

2、我們會在當前目錄生成以下結構的目錄

/
├── app 
│   ├── api
│   ├── model
│   └── service
├── boot
├── config
├── docker
├── document
├── i18n
├── library
├── public
├── router
├── template
├── vendor
├── Dockerfile
├── go.mod
└── main.go

詳細的目錄說明見官網 傳送門

2、建立路由與jwt相關檔案

1、開啟 router/router.go檔案

package router

import (
    "gadmin/app/api/admin/login"
    "gadmin/app/api/admin/rbac"
    "gadmin/app/api/admin/user"
    "gadmin/app/middleware/token"

    "github.com/gogf/gf/frame/g"
    "github.com/gogf/gf/net/ghttp"
)

    func init() {
        s := g.Server()
        s.Group("/v1", func(group *ghttp.RouterGroup) {
            // 登入邏輯走login包
           group.POST("/admin/loginSubmit", login.GfJWTMiddleware.LoginHandler)
           group.Group("/admin", func(group *ghttp.RouterGroup) {
               // 中介軟體檢測token是否有效
              group.Middleware(token.Validator)
               // 重新整理token令牌
              group.GET("/refresh", login.GfJWTMiddleware.RefreshHandler)
            })
        })
    }
...

2、開啟 app/api/admin/login 檔案

package login

import (
    "errors"
    "gadmin/app/model/users"
    "gadmin/library/base"
    "gadmin/library/helper"
    "gadmin/library/input"
    "gadmin/library/redis"
    "time"

    "github.com/gogf/gf/util/gconv"

    "github.com/gogf/gf/crypto/gmd5"
    "github.com/gogf/gf/frame/g"

    "github.com/gogf/gf/util/gvalid"

    "github.com/gogf/gf/net/ghttp"

    jwt "github.com/gogf/gf-jwt"
    "github.com/gogf/gf/os/glog"
)

var (
    GfJWTMiddleware *jwt.GfJWTMiddleware // 宣告jwt包的全域性變數
)

type SignRequest struct {
    Username string `v:required#賬號不能為空 json:"username"`
    Password string `v:required#密碼不能為空 json:"password"`
}

func init() {
    authMiddleWare, err := jwt.New(&jwt.GfJWTMiddleware{
        Realm:           "test zhou",           // 用於展示中介軟體的名稱
        Key:             []byte("secret key"),  // 金鑰
        Timeout:         time.Minute * 5,       // token過期時間
        MaxRefresh:      time.Minute * 5,
        IdentityKey:     "id",                  // 身份驗證的key值
        TokenLookup:     "header: Authorization, query: token, cookie: jwt",    // token檢索模式,用於提取token-> Authorization
        TokenHeadName:   "Bearer",              // token在請求頭時的名稱,預設值為Bearer
                                                // 客戶端在header中傳入Authorization 對一個值是Bearer + 空格 + token
        TimeFunc:        time.Now,              // 測試或伺服器在其他時區可設定該屬性
        Authenticator:   Authenticator,         // 根據登入資訊對使用者進行身份驗證的回撥函式
        LoginResponse:   LoginResponse,         // 完成登入後返回的資訊,使用者可自定義返回資料,預設返回
        RefreshResponse: auth.RefreshResponse,  // 重新整理token後返回的資訊,使用者可自定義返回資料,預設返回
        Unauthorized:    auth.Unauthorized,     // 處理不進行授權的邏輯
        IdentityHandler: auth.IdentityHandler,  // 解析並設定使用者身份資訊
        PayloadFunc:     auth.PayloadFunc,      // 登入期間的回撥的函式
    })

    if err != nil {
        glog.Error("JWT Error:" + err.Error())
    }

    GfJWTMiddleware = authMiddleWare
}

// Authenticator 檢測身份資訊是否正常
func Authenticator(r *ghttp.Request) (interface{}, error) {
    var req *SignRequest
    // 接收引數
    input.JSONToStruct(r, &req)

    // 校驗資料引數
    if err := gvalid.CheckStruct(req, nil); err != nil {
        base.FailParam(r, err.String())
    }

    // 查詢資料
    res := users.GetOne(g.Map{"username": req.Username})

    if res.Id <= 0 {
        return nil, errors.New("使用者名稱或密碼錯誤")
    }

    reqPwd, errPwd := gmd5.Encrypt(req.Password + res.Salt)
    if errPwd != nil {
        glog.Error("md5加密異常", errPwd)
        return nil, errors.New("伺服器異常")
    }

    if reqPwd != res.Password {
        return nil, errors.New("使用者名稱或密碼錯誤~")
    }

    // 設定引數儲存到請求中
    r.SetParam("uuid", res.Uuid)

    return g.Map{"username": res.Username, "uuid": res.Uuid}, nil
}

// PostLogin 返回對應的使用者資訊
func PostLogin(r *ghttp.Request, code int, token string, expire time.Time) {
    j, _ := r.GetJson()
    // 格式化時間
    t := helper.TimeToString(expire)

    // 獲取配置檔案中的redis字首
    var loginPrefix = g.Cfg("redis").Get("APP.LOGIN_PREFIX")
    redis.Set(gconv.String(loginPrefix)+gconv.String(r.GetParam("uuid")), token)

    base.Success(r, g.Map{
        "username": j.GetString("username"),
        "token":    token,
        "expire":   t,
    })
}

// RefreshResponse 重新整理token資訊
func RefreshResponse(r *ghttp.Request, code int, token string, expire time.Time) {
    base.Success(r, g.Map{"token": token, "expire": helper.TimeToString(expire)})
}

// Unauthorized 返回驗證錯誤資訊
func Unauthorized(r *ghttp.Request, code int, message string) {
    // TODO 英文提示可在此處轉換為中文,暫沒找到好的方式後續單做一個配置檔案
    base.FailParam(r, message)
}

// IdentityHandler sets the identity for JWT.
func IdentityHandler(r *ghttp.Request) interface{} {
    claims := jwt.ExtractClaims(r)
    return claims["id"]
}

// PayloadFunc 給token中新增其它欄位的資料
func PayloadFunc(data interface{}) jwt.MapClaims {
    claims := jwt.MapClaims{}
    params := data.(map[string]interface{})
    if len(params) > 0 {
        for k, v := range params {
            claims[k] = v
        }
    }
    return claims
}

3、 開啟 app/middleware/token 檢測jwt是否有效的中介軟體

package token

import (
    "gadmin/app/api/admin/login"
    "gadmin/library/base"
    "gadmin/library/redis"

    "github.com/gogf/gf/frame/g"
    "github.com/gogf/gf/net/ghttp"
    "github.com/gogf/gf/util/gconv"
)

// Validator 驗證token有效性
func Validator(r *ghttp.Request) {
    login.GfJWTMiddleware.MiddlewareFunc()(r)
    // 解析token
    parseToken, _ := login.GfJWTMiddleware.ParseToken(r)
    var token = parseToken.Raw
    // 解析otken中儲存的內容
    var claims = gconv.Map(parseToken.Claims)
    // 登入時存了一份到redis中  驗證時需要在校驗一次
    if !GetRedisToken(gconv.String(claims["uuid"]), token) {
        base.Fail(r, "重新登入")
    }
    r.Middleware.Next()
}

// GetRedisToken 獲取快取中的token與客戶端token對比
func GetRedisToken(uuid string, oldToken string) bool {
    redisPrefix := gconv.String(g.Cfg("redis").Get("APP.LOGIN_PREFIX"))
    key := redisPrefix + uuid
    if redis.Get(key) != oldToken {
        return false
    }
    return gconv.Bool(true)
}

到此 jwt使用就已經完成,希望對大家有幫助

截圖說明

請求登入成功
使用GoFrame框架 JWT方式驗證使用者資訊

拿登入成功的token 重新整理 token
使用GoFrame框架 JWT方式驗證使用者資訊

未使用token請求重新整理介面
使用GoFrame框架 JWT方式驗證使用者資訊

後面會出原生的jwt實現方式,使用外掛比較快速,

以後也會出casbin許可權認證

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章