衔接上文,继续开发。
现在的api是可以随意调用的,我们采用jwt-go来解决这个问题。
JWT 就是Json Web Token
一个JWT一般包含3个部分:
使用JWT进行身份校验
下载依赖
go get -u github.com/dgrijalva/jwt–go
编写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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
package util import ( "fmt" "ginBlog/pkg/setting" "time" jwt "github.com/dgrijalva/jwt-go" ) var jwtSecret = []byte(setting.GetAPPJwtSecret()) //Claims 不知道是啥 type Claims struct { UserName string `json:"username"` Password string `json:"password"` jwt.StandardClaims } //GenerateToken 根据账户名和密码,生成token 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: "ginBlog", }, } //NewWithClaims 生成Token tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) //SignedString 生成签名字符串 token, err := tokenClaims.SignedString(jwtSecret) if err != nil { fmt.Println(err) } return token, err } //ParseToken 解析Token func ParseToken(token string) (*Claims, error) { //解析鉴权的声明,返回Token tokenClaims, err := jwt.ParseWithClaims( token, &Claims{}, func(token *jwt.Token) (interface{}, error) { return jwtSecret, nil }, ) // if tokenClaims != nil { //数据类型转换 claims, ok := tokenClaims.Claims.(*Claims) //验证 if ok && tokenClaims.Valid { return claims, nil } } return nil, err } |
工具包涉及知识点都注释在代码里了。
编写Gin的中间件
有了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 45 |
package jwt import ( "ginBlog/pkg/e" "ginBlog/pkg/util" "net/http" "time" "github.com/gin-gonic/gin" ) //JWT 校验中间函数 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.InvalidParams } else { claims, err := util.ParseToken(token) if err != nil { code = e.ErrorAuthCheckTokenFailed } else if time.Now().Unix() > claims.ExpiresAt { code = e.ErrorAuthCheckTokenTimeOut } } if code != e.Success { c.JSON(http.StatusUnauthorized, gin.H{ "code": code, "msg": e.GetMsg(code), "data": data, }) c.Abort() return } c.Next() } } |
这个函数其实就是控制了gin的上下文是返回错误,还是继续执行。
获取Token
这里我们就需要新增一个获取Token的API,需要实现Auth模块。
实现Auth的Models模块
在models下新建auth.go文件,写入内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package models //Auth 用户 type Auth struct { ID int `gorm:"primary_key" json:"id"` Username string `json:"username"` Password string `json:"password"` } //CheckAuth 判断Auth是否存在 func CheckAuth(username, password string) bool { var auth Auth db.Select("id").Where(Auth{Username: username, Password: password}).First(&auth) return auth.ID > 0 } |
实现Auth的路由功能
在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 57 |
package api import ( "ginBlog/models" "ginBlog/pkg/e" "ginBlog/pkg/util" "log" "net/http" "github.com/astaxie/beego/validation" "github.com/gin-gonic/gin" ) type auth struct { Username string `valid:"Required; MaxSize(50)"` Password string `valid:"Required; MaxSize(50)"` } //GetAuth 获取token 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.InvalidParams if ok { isExist := models.CheckAuth(username, password) if isExist { token, err := util.GenerateToken(username, password) if err != nil { code = e.ErrorAuthToken } else { data["token"] = token code = e.Success } } else { code = e.ErrorAuth } } 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, }) } |
这里需要注意 auth 类型,它把valid写在类对象后面,然后直接调用 valid.Valid(&a) 来验证。
然后接下来我们在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 |
package routers import ( "ginBlog/pkg/setting" "ginBlog/routers/api" v1 "ginBlog/routers/api/v1" "github.com/gin-gonic/gin" ) //InitRouter 初始化路由 func InitRouter() *gin.Engine { r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) gin.SetMode(setting.GetRunMode()) //账户路由 { r.Group("/api").GET("/auth", api.GetAuth) } apiv1 := r.Group("/api/v1") { ... } return r } |
新增了账户路由部分。
验证获取Token
GET请求 192.168.1.101:8000/api/auth?username=root&password=root
验证的过程中发现了代码的一些问题,所以我做了一些相应的修复。
最后我们得到结果如下:
1 2 3 4 5 6 7 |
{ "code": 200, "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJwYXNzd29yZCI6InJvb3QiLCJleHAiOjE1OTkyMjUyMjIsImlzcyI6ImdpbkJsb2cifQ.QbCJIfNW6as1kvhSetW_RpdCZ-gDG2f9sR8A9XTe2ds" }, "msg": "ok" } |
Gin接入JWT中间件
接下来我们把JWT中间件接入到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 31 32 33 34 35 36 37 38 39 40 41 42 |
package routers import ( "ginBlog/middleware/jwt" "ginBlog/pkg/setting" "ginBlog/routers/api" v1 "ginBlog/routers/api/v1" "github.com/gin-gonic/gin" ) //InitRouter 初始化路由 func InitRouter() *gin.Engine { r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) gin.SetMode(setting.GetRunMode()) //账户路由 { r.Group("/api").GET("/auth", api.GetAuth) } apiv1 := r.Group("/api/v1") apiv1.Use(jwt.JWT()) { //标签路由 apiv1.GET("/tags", v1.GetTags) apiv1.POST("/tags", v1.AddTag) apiv1.PUT("/tags/:id", v1.EditTag) apiv1.DELETE("/tags/:id", v1.DeleteTag) //文章路由 apiv1.GET("/articles", v1.GetArticles) apiv1.GET("/articles/:id", v1.GetArticle) apiv1.POST("/articles", v1.AddArticle) apiv1.PUT("/articles/:id", v1.EditArticle) apiv1.DELETE("/articles/:id", v1.DeleteArticle) } 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 30 31 32 33 34 |
[xiong@AMDServer ginBlog]$ tree . ├── conf │ └── app.ini ├── go.mod ├── go.sum ├── 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 12 directories, 18 files |
验证功能
最后我们要验证一下,我们加入的jwt校验是不是成功了。
这种情况下,我们先请求一篇文章:
GET 192.168.1.101:8000/api/v1/articles
给出反馈是:
1 2 3 4 5 |
{ "code": 400, "data": null, "msg": "无效参数" } |
然后我们加上token:
GET 192.168.1.101:8000/api/v1/articles?token=23123
1 2 3 4 5 |
{ "code": 20001, "data": null, "msg": "Token鉴权失败" } |
这里说明我们需要先获取Token,然后再请求其他api。
GET 192.168.1.101:8000/api/auth?username=root&password=root
得到token:
1 2 3 4 5 6 7 |
{ "code": 200, "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJwYXNzd29yZCI6InJvb3QiLCJleHAiOjE1OTkyMjYyNTEsImlzcyI6ImdpbkJsb2cifQ.xj9PZjdD83PPC3ajwWcvi_omQOiIsWxLYRMq6o2ergM" }, "msg": "ok" } |
然后我们带上token再去请求应用:
192.168.1.101:8000/api/v1/articles?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJwYXNzd29yZCI6InJvb3QiLCJleHAiOjE1OTkyMjYyNTEsImlzcyI6ImdpbkJsb2cifQ.xj9PZjdD83PPC3ajwWcvi_omQOiIsWxLYRMq6o2ergM
最后得到结果:
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 |
{ "code": 200, "data": { "lists": [ { "id": 2, "created_on": 1599048263, "modified_on": 0, "tag_id": 8, "tag": { "id": 8, "created_on": 1599026312, "modified_on": 0, "name": "create1", "created_by": "root", "modified_by": "", "state": 1 }, "title": "test-title", "desc": "test-desc", "content": "test-content", "created_by": "root", "Modified_by": "", "state": 1 } ], "total": 1 }, "msg": "ok" } |
到这里我们的JWT校验就算完成了。