使用 JWT 进行身份校验

举报
炒香菇的书呆子 发表于 2023/04/30 23:04:01 2023/04/30
【摘要】 使用 JWT 进行身份校验涉及知识点JWT本文目标在前面几节中,我们已经基本的完成了 API’s 的编写,但是,还存在一些非常严重的问题,例如,我们现在的 API 是可以随意调用的,这显然还不安全全,在本文中我们通过 jwt-go (GoDoc)的方式来简单解决这个问题。下载依赖包首先,我们下载 jwt-go 的依赖包,如下:go get -u github.com/dgrijalva/jw...

使用 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{},其包含SigningMethodHS256、SigningMethodHS384、SigningMethodHS512三种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-go在Gin中的验证就完成了!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。