「连载五」使用 JWT 进行身份校验

  1. 涉及知识点
  2. 本文目标
  3. 下载依赖包
  4. 编写 jwt 工具包
  5. 如何获取Token
  6. 验证Token
  7. 将中间件接入Gin
  8. 验证功能
  9. 参考
    1. 本系列示例代码
  10. 关于
    1. 修改记录
    1. 我的公众号

涉及知识点

  • JWT

本文目标

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

下载依赖包

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

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

编写 jwt 工具包

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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文件,写入内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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文件,写入内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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文件,写入内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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 的方法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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,查看返回值是否正确

1
2
3
4
5
6
7
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6InRlc3QxMjM0NTYiLCJleHAiOjE1MTg3MjAwMzcsImlzcyI6Imdpbi1ibG9nIn0.-kK0V9E06qTHOzupQM_gHXAGDB3EJtJS4H5TTCyWwW8"
},
"msg": "ok"
}

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

将中间件接入Gin

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
}

当前目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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编写就完成啦!

验证功能

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

正确的反馈应该是

1
2
3
4
5
6
7
8
9
10
11
{
"code": 400,
"data": null,
"msg": "请求参数错误"
}

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

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

1
2
3
4
5
6
7
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6InRlc3QxMjM0NTYiLCJleHAiOjE1MTg3MjQ2OTMsImlzcyI6Imdpbi1ibG9nIn0.KSBY6TeavV_30kfmP7HWLRYKP5TPEDgHtABe9HCsic4"
},
"msg": "ok"
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"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中的验证就完成了!

参考

本系列示例代码

关于

修改记录

  • 第一版:2018 年 02 月 16 日发布文章
  • 第二版:2019 年 10 月 01 日修改文章

如果有任何疑问或错误,欢迎在 issues 进行提问或给予修正意见,如果喜欢或对你有所帮助,欢迎 Star,对作者是一种鼓励和推进。

我的公众号

image


免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com

推荐阅读:

文章标题:「连载五」使用 JWT 进行身份校验

本文作者:知识铺

发布时间:2018-02-14, 12:00:00

最后更新:2020-04-19, 17:52:49

原始链接:https://blog.zshipu.com/2018/02/14/golang/go/gin/2018-02-14-jwt/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏