Go Gin 系列五:使用 JWT 进行身份校验

涉及知识点

  • JWT

本文目标

在前面几节中,我们已经基本的完成了 API's 的编写,但是,还存在一些非常严重的问题,例如,我们现在的 API 是可以随意调用的,这显然还不安全全,在本文中我们通过 jwt-go (GoDoc)的方式来简单解决这个问题。

下载依赖包

首先,我们下载 jwt-go 的依赖包,如下:

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

编写 jwt 工具包

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

package util


import (
	"time"


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


	"github.com/EDDYCJY/go-gin-example/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"


	"github.com/EDDYCJY/go-gin-example/pkg/util"
	"github.com/EDDYCJY/go-gin-example/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()
	}
}

如何获取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"


	"github.com/EDDYCJY/go-gin-example/pkg/e"
	"github.com/EDDYCJY/go-gin-example/pkg/util"
	"github.com/EDDYCJY/go-gin-example/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"


    "github.com/EDDYCJY/go-gin-example/routers/api"
    "github.com/EDDYCJY/go-gin-example/routers/api/v1"
    "github.com/EDDYCJY/go-gin-example/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

获取token的 API 方法就到这里啦,让我们来测试下是否可以正常使用吧!

重启服务后,用GET方式访问http://127.0.0.1:8000/auth?username=test&password=test123456,查看返回值是否正确

{
  "code": 200,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6InRlc3QxMjM0NTYiLCJleHAiOjE1MTg3MjAwMzcsImlzcyI6Imdpbi1ibG9nIn0.-kK0V9E06qTHOzupQM_gHXAGDB3EJtJS4H5TTCyWwW8"
  },
  "msg": "ok"
}

我们有了token的 API,也调用成功了

将中间件接入Gin

2、 接下来我们将中间件接入到Gin的访问流程中

我们打开routers目录下的router.go文件,修改文件内容(新增引用包和中间件引用)

package routers


import (
    "github.com/gin-gonic/gin"


    "github.com/EDDYCJY/go-gin-example/routers/api"
    "github.com/EDDYCJY/go-gin-example/routers/api/v1"
    "github.com/EDDYCJY/go-gin-example/pkg/setting"
    "github.com/EDDYCJY/go-gin-example/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
}

当前目录结构:

go-gin-example/
├── 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

到这里,我们的JWT编写就完成啦!

验证功能

我们来测试一下,再次访问

  • http://127.0.0.1:8000/api/v1/articles

  • http://127.0.0.1:8000/api/v1/articles?token=23131

正确的反馈应该是

{
  "code": 400,
  "data": null,
  "msg": "请求参数错误"
}


{
  "code": 20001,
  "data": null,
  "msg": "Token鉴权失败"
}


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

{
  "code": 200,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6InRlc3QxMjM0NTYiLCJleHAiOjE1MTg3MjQ2OTMsImlzcyI6Imdpbi1ibG9nIn0.KSBY6TeavV_30kfmP7HWLRYKP5TPEDgHtABe9HCsic4"
  },
  "msg": "ok"
}

再用包含token的 URL 参数去访问我们的应用 API,

访问http://127.0.0.1:8000/api/v1/articles?token=eyJhbGci...,检查接口返回值

{
  "code": 200,
  "data": {
    "lists": [
      {
        "id": 2,
        "created_on": 1518700920,
        "modified_on": 0,
        "tag_id": 1,
        "tag": {
          "id": 1,
          "created_on": 1518684200,
          "modified_on": 0,
          "name": "tag1",
          "created_by": "",
          "modified_by": "",
          "state": 0
        },
        "content": "test-content",
        "created_by": "test-created",
        "modified_by": "",
        "state": 0
      }
    ],
    "total": 1
  },
  "msg": "ok"
}

返回正确,至此我们的jwt-goGin中的验证就完成了!

参考

本系列示例代码

  • go-gin-example

我的公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值