美文网首页go
gin+gorm HelloWorld(使用JWT进行身份校验)

gin+gorm HelloWorld(使用JWT进行身份校验)

作者: 我就是小政政 | 来源:发表于2019-02-26 14:15 被阅读0次

    使用JWT进行身份校验

    但是,还存在一些非常严重的问题,例如,我们现在的API是可以随意调用的,这显然还不够完美,是有问题的

    那么我们采用 jwt-goGoDoc)的方式来简单解决这个问题

    下载依赖包

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

    编写jwt工具包

    我们需要编写一个jwt的工具包,我们在pkg下的util目录新建jwt.go,写入文件内容:

    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
    }
    

    在这个工具包,我们涉及到

    • NewWithClaims(method SigningMethod, claims Claims)method对应着SigningMethodHMAC struct{},其包含SigningMethodHS256SigningMethodHS384SigningMethodHS512三种crypto.Hash方案
    • func (t *Token) SignedString(key interface{}) 该方法内部生成签名字符串,再用于获取完整、已签名的token
    • func (p *Parser) ParseWithClaims 用于解析鉴权的声明,方法内部主要是具体的解码和校验的过程,最终返回*Token
    • func (m MapClaims) Valid() 验证基于时间的声明exp, iat, nbf,注意如果没有任何声明在令牌中,仍然会被认为是有效的。并且对于时区偏差没有计算方法

    有了jwt工具包,接下来我们要编写要用于Gin的中间件,我们在middleware下新建jwt目录,新建jwt.go文件,写入内容:

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

    这个中间件涉及到:

    • gin使用中间件一般是在引擎Engine初始化的时候就绑定,也就是说在需要使用中间件才能访问资源的接口之前干这些事情。比如对登录操作放行,对其他操作进行鉴权。

    • 中间件里面有错误如果不想继续后续接口的调用不能直接return,而是应该调用c.Abort()方法。

    如何获取Token

    那么我们如何调用它呢,我们还要获取Token呢?
    1、 我们要新增一个获取Token的API
    在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)
        if auth.ID > 0 {
            return true
        }
    
        return false
    }
    

    在routers下的api目录新建auth.go文件,写入内容:

    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文件,修改文件内容(新增获取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
    }
    
    验证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文件,修改文件内容(新增引用包和中间件引用)

    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
    }
    
    
    当前结构目录
    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
    
    验证功能
    • 访问http://127.0.0.1:8000/api/v1/articles
    {
        "code": 400,
        "data": null,
        "msg": "请求参数错误"
    }
    
    • 访问http://127.0.0.1:8000/api/v1/articles?token=23131
    {
        "code": 20001,
        "data": null,
        "msg": "Token鉴权失败"
    }
    

    我们需要访问http://127.0.0.1:8000/auth?username=test&password=test123456,得到token

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

    再用包含token的URL参数去访问我们的应用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方案对比等参考

    九幅图理解使用JSON Web Token(JWT)设计单点登录系统
    https://blog.csdn.net/uniquewonderq/article/details/79720897
    使用JWT做微服务的登录方案 https://blog.csdn.net/w57685321/article/details/79463837

    相关文章

      网友评论

        本文标题:gin+gorm HelloWorld(使用JWT进行身份校验)

        本文链接:https://www.haomeiwen.com/subject/rvkayqtx.html