gin+gorm HelloWorld(使用JWT進(jìn)行身份校驗(yàn))

使用JWT進(jìn)行身份校驗(yàn)

但是,還存在一些非常嚴(yán)重的問題月洛,例如婉徘,我們現(xiàn)在的API是可以隨意調(diào)用的,這顯然還不夠完美敬矩,是有問題的

那么我們采用 jwt-goGoDoc)的方式來簡(jiǎn)單解決這個(gè)問題

下載依賴包

go get -u github.com/dgrijalva/jwt-go

編寫jwt工具包

我們需要編寫一個(gè)jwt的工具包概行,我們?cè)趐kg下的util目錄新建jwt.go,寫入文件內(nèi)容:

package util

import (
    "time"

    jwt "github.com/dgrijalva/jwt-go"

    "gin-blog/pkg/setting"
)

var jwtSecret = []byte(setting.JwtSecret)

type Claims struct {
    Username string `json:"username"`
    Password string `json:"password"`
    jwt.StandardClaims
}

func GenerateToken(username, password string) (string, error) {
    nowTime := time.Now()
    expireTime := nowTime.Add(3 * time.Hour)

    claims := Claims{
        username,
        password,
        jwt.StandardClaims {
            ExpiresAt : expireTime.Unix(),
            Issuer : "gin-blog",
        },
    }

    tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    token, err := tokenClaims.SignedString(jwtSecret)

    return token, err
}

func ParseToken(token string) (*Claims, error) {
    tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return jwtSecret, nil
    })

    if tokenClaims != nil {
        if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
            return claims, nil
        }
    }

    return nil, err
}

在這個(gè)工具包弧岳,我們涉及到

  • NewWithClaims(method SigningMethod, claims Claims)凳忙,method對(duì)應(yīng)著SigningMethodHMAC struct{},其包含SigningMethodHS256缩筛、SigningMethodHS384消略、SigningMethodHS512三種crypto.Hash方案
  • func (t *Token) SignedString(key interface{}) 該方法內(nèi)部生成簽名字符串,再用于獲取完整瞎抛、已簽名的token
  • func (p *Parser) ParseWithClaims 用于解析鑒權(quán)的聲明艺演,方法內(nèi)部主要是具體的解碼和校驗(yàn)的過程,最終返回*Token
  • func (m MapClaims) Valid() 驗(yàn)證基于時(shí)間的聲明exp, iat, nbf桐臊,注意如果沒有任何聲明在令牌中胎撤,仍然會(huì)被認(rèn)為是有效的。并且對(duì)于時(shí)區(qū)偏差沒有計(jì)算方法

有了jwt工具包断凶,接下來我們要編寫要用于Gin的中間件伤提,我們?cè)趍iddleware下新建jwt目錄,新建jwt.go文件认烁,寫入內(nèi)容:

package jwt

import (
    "time"
    "net/http"

    "github.com/gin-gonic/gin"

    "gin-blog/pkg/util"
    "gin-blog/pkg/e"
)

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 {
            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()
    }
}

這個(gè)中間件涉及到:

  • gin使用中間件一般是在引擎Engine初始化的時(shí)候就綁定肿男,也就是說在需要使用中間件才能訪問資源的接口之前干這些事情介汹。比如對(duì)登錄操作放行,對(duì)其他操作進(jìn)行鑒權(quán)舶沛。

  • 中間件里面有錯(cuò)誤如果不想繼續(xù)后續(xù)接口的調(diào)用不能直接return嘹承,而是應(yīng)該調(diào)用c.Abort()方法。

如何獲取Token

那么我們?nèi)绾握{(diào)用它呢如庭,我們還要獲取Token呢叹卷?
1、 我們要新增一個(gè)獲取Token的API
在models下新建auth.go文件坪它,寫入內(nèi)容:

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)
    if auth.ID > 0 {
        return true
    }

    return false
}

在routers下的api目錄新建auth.go文件骤竹,寫入內(nèi)容:

package api

import (
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/astaxie/beego/validation"

    "gin-blog/pkg/e"
    "gin-blog/pkg/util"
    "gin-blog/models"
)

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, 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文件,修改文件內(nèi)容(新增獲取token的方法):

package routers

import (
    "github.com/gin-gonic/gin"
    
    "gin-blog/routers/api"
    "gin-blog/routers/api/v1"
    "gin-blog/pkg/setting"
)

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
}
驗(yàn)證Token

訪問 GET http://localhost:8000/auth?username=test&password=test123456

{
    "code": 200,
    "data": {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6InRlc3QxMjM0NTYiLCJleHAiOjE1NTExNzk4MzEsImlzcyI6Imdpbi1ibG9nIn0.bE0agND6A_fyyZz_UeR_YtdBvOrC2pjTPtL47SuAYWI"
    },
    "msg": "ok"
}
將中間件接入Gin

2往毡、 接下來我們將中間件接入到Gin的訪問流程中
我們打開routers目錄下的router.go文件蒙揣,修改文件內(nèi)容(新增引用包和中間件引用)

package routers

import (
    "github.com/gin-gonic/gin"
    
    "gin-blog/routers/api"
    "gin-blog/routers/api/v1"
    "gin-blog/pkg/setting"
    "gin-blog/middleware/jwt"
)

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
}

當(dāng)前結(jié)構(gòu)目錄
gin-blog/
├── conf
│   └── app.ini
├── main.go
├── middleware
│   └── jwt
│       └── jwt.go
├── models
│   ├── article.go
│   ├── auth.go
│   ├── models.go
│   └── tag.go
├── pkg
│   ├── e
│   │   ├── code.go
│   │   └── msg.go
│   ├── setting
│   │   └── setting.go
│   └── util
│       ├── jwt.go
│       └── pagination.go
├── routers
│   ├── api
│   │   ├── auth.go
│   │   └── v1
│   │       ├── article.go
│   │       └── tag.go
│   └── router.go
├── runtime
驗(yàn)證功能
  • 訪問http://127.0.0.1:8000/api/v1/articles
{
    "code": 400,
    "data": null,
    "msg": "請(qǐng)求參數(shù)錯(cuò)誤"
}
  • 訪問http://127.0.0.1:8000/api/v1/articles?token=23131
{
    "code": 20001,
    "data": null,
    "msg": "Token鑒權(quán)失敗"
}

我們需要訪問http://127.0.0.1:8000/auth?username=test&password=test123456,得到token

{
    "code": 200,
    "data": {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6InRlc3QxMjM0NTYiLCJleHAiOjE1NTExODA1MjgsImlzcyI6Imdpbi1ibG9nIn0.dcbLkez5QDrgierHmWWYMRBoyLPawSsLew32C_IARz0"
    },
    "msg": "ok"
}

再用包含token的URL參數(shù)去訪問我們的應(yīng)用API卖擅,
訪問http://127.0.0.1:8000/api/v1/articles?token=eyJhbGci...鸣奔,檢查接口返回值

{
    "code": 200,
    "data": {
        "lists": [
            {
                "id": 1,
                "created_on": 0,
                "modified_on": 0,
                "title": "夸父逐日",
                "desc": "\b夸父鍥而不舍追求太陽最終失敗的\b\b故事",
                "content": "\baaabbbcccdddeee夸父",
                "created_by": "test",
                "modified_by": "",
                "state": 1,
                "tag_id gorm:": 4,
                "tag": {
                    "id": 4,
                    "created_on": 1551077861,
                    "modified_on": 0,
                    "name": "百科全書",
                    "created_by": "test",
                    "modified_by": "",
                    "state": 1
                }
            }
        ],
        "total": 1
    },
    "msg": "ok"
}

jwt與session方案對(duì)比等參考

九幅圖理解使用JSON Web Token(JWT)設(shè)計(jì)單點(diǎn)登錄系統(tǒng)
https://blog.csdn.net/uniquewonderq/article/details/79720897
使用JWT做微服務(wù)的登錄方案 https://blog.csdn.net/w57685321/article/details/79463837

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惩阶,隨后出現(xiàn)的幾起案子挎狸,更是在濱河造成了極大的恐慌,老刑警劉巖断楷,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锨匆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡冬筒,警方通過查閱死者的電腦和手機(jī)恐锣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舞痰,“玉大人土榴,你說我怎么就攤上這事∠炫#” “怎么了玷禽?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)呀打。 經(jīng)常有香客問我矢赁,道長(zhǎng),這世上最難降的妖魔是什么贬丛? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任撩银,我火速辦了婚禮,結(jié)果婚禮上豺憔,老公的妹妹穿的比我還像新娘额获。我一直安慰自己够庙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布咪啡。 她就那樣靜靜地躺著首启,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撤摸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天褒纲,我揣著相機(jī)與錄音准夷,去河邊找鬼。 笑死莺掠,一個(gè)胖子當(dāng)著我的面吹牛衫嵌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播彻秆,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼楔绞,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了唇兑?” 一聲冷哼從身側(cè)響起酒朵,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扎附,沒想到半個(gè)月后蔫耽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡留夜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年匙铡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碍粥。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鳖眼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嚼摩,到底是詐尸還是另有隱情钦讳,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布低斋,位于F島的核電站蜂厅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏膊畴。R本人自食惡果不足惜掘猿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唇跨。 院中可真熱鬧稠通,春花似錦衬衬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至飞主,卻和暖如春狮惜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碌识。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工碾篡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筏餐。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓开泽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親魁瞪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子穆律,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容