說明
- 框架使用 GoFrame 國產框架
強哥
開發 - jwt使用 https://github.com/gogf/gf-jwt
鵬哥
編寫的 - 許可權使用
casbin
外掛 地址
使用說明
官網的 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使用就已經完成,希望對大家有幫助
截圖說明
請求登入成功
拿登入成功的token 重新整理 token
未使用token請求重新整理介面
後面會出原生的jwt實現方式,使用外掛比較快速,
以後也會出casbin
許可權認證
本作品採用《CC 協議》,轉載必須註明作者和本文連結