Gin实践 连载五 搭建Blog API's(四)

2018-03-01 10:52:33来源:segmentfault作者:EDDYCJY人点击

分享
API's - JSON Web Tokens (JWT)

在前面几节中,我们已经基本的完成了API's的编写


但是,还存在一些非常严重的问题,例如,我们现在的API是可以随意调用的,这显然还不够完美,是有问题的


那么我们采用 jwt-go (GoDoc)的方式来简单解决这个问题

下载依赖包

首先,我们下载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对应着SigningMethodHMACstruct{},其包含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 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 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 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 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 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_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"] = 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/articleshttp://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

相关文章

    无相关信息

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台