- 使用JWT进行身份校验
- 下载依赖包
- 编写
jwt工具包 - 如何获取
Token - 验证
Token - 将中间件接入
Gin - 验证功能
- 参考
- 本系列示例代码
- 本系列示例代码
使用JWT进行身份校验
在前面几节中,我们已经基本的完成了API’s的编写
但是,还存在一些非常严重的问题,例如,我们现在的API是可以随意调用的,这显然还不够完美,是有问题的
那么我们采用 jwt-go (GoDoc)的方式来简单解决这个问题
项目地址:https://github.com/EDDYCJY/go-gin-example
下载依赖包
首先,我们下载jwt-go的依赖包
go get -u github.com/dgrijalva/jwt-go
编写jwt工具包
我们需要编写一个jwt的工具包,我们在pkg下的util目录新建jwt.go,写入文件内容:
package utilimport ("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{},其包含SigningMethodHS256、SigningMethodHS384、SigningMethodHS512三种crypto.Hash方案func (t *Token) SignedString(key interface{})该方法内部生成签名字符串,再用于获取完整、已签名的tokenfunc (p *Parser) ParseWithClaims用于解析鉴权的声明,方法内部主要是具体的解码和校验的过程,最终返回*Tokenfunc (m MapClaims) Valid()验证基于时间的声明exp, iat, nbf,注意如果没有任何声明在令牌中,仍然会被认为是有效的。并且对于时区偏差没有计算方法
有了jwt工具包,接下来我们要编写要用于Gin的中间件,我们在middleware下新建jwt目录,新建jwt.go文件,写入内容:
package jwtimport ("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 intvar data interface{}code = e.SUCCESStoken := 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 modelstype 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 Authdb.Select("id").Where(Auth{Username : username, Password : password}).First(&auth)if auth.ID > 0 {return true}return false}
在routers下的api目录新建auth.go文件,写入内容:
package apiimport ("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_PARAMSif 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"] = tokencode = 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 routersimport ("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
获取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 routersimport ("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
到这里,我们的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中的验证就完成了!
参考
本系列示例代码
- go-gin-example
