• 使用JWT进行身份校验
  • 下载依赖包
  • 编写jwt工具包
  • 如何获取Token
  • 验证Token
  • 将中间件接入Gin
  • 验证功能
  • 参考
    • 本系列示例代码

    使用JWT进行身份校验

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

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

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

    项目地址:https://github.com/EDDYCJY/go-gin-example


    下载依赖包

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

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

    编写jwt工具包

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

    1. package util
    2. import (
    3. "time"
    4. jwt "github.com/dgrijalva/jwt-go"
    5. "gin-blog/pkg/setting"
    6. )
    7. var jwtSecret = []byte(setting.JwtSecret)
    8. type Claims struct {
    9. Username string `json:"username"`
    10. Password string `json:"password"`
    11. jwt.StandardClaims
    12. }
    13. func GenerateToken(username, password string) (string, error) {
    14. nowTime := time.Now()
    15. expireTime := nowTime.Add(3 * time.Hour)
    16. claims := Claims{
    17. username,
    18. password,
    19. jwt.StandardClaims {
    20. ExpiresAt : expireTime.Unix(),
    21. Issuer : "gin-blog",
    22. },
    23. }
    24. tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    25. token, err := tokenClaims.SignedString(jwtSecret)
    26. return token, err
    27. }
    28. func ParseToken(token string) (*Claims, error) {
    29. tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
    30. return jwtSecret, nil
    31. })
    32. if tokenClaims != nil {
    33. if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
    34. return claims, nil
    35. }
    36. }
    37. return nil, err
    38. }

    在这个工具包,我们涉及到

    • 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. package jwt
    2. import (
    3. "time"
    4. "net/http"
    5. "github.com/gin-gonic/gin"
    6. "gin-blog/pkg/util"
    7. "gin-blog/pkg/e"
    8. )
    9. func JWT() gin.HandlerFunc {
    10. return func(c *gin.Context) {
    11. var code int
    12. var data interface{}
    13. code = e.SUCCESS
    14. token := c.Query("token")
    15. if token == "" {
    16. code = e.INVALID_PARAMS
    17. } else {
    18. claims, err := util.ParseToken(token)
    19. if err != nil {
    20. code = e.ERROR_AUTH_CHECK_TOKEN_FAIL
    21. } else if time.Now().Unix() > claims.ExpiresAt {
    22. code = e.ERROR_AUTH_CHECK_TOKEN_TIMEOUT
    23. }
    24. }
    25. if code != e.SUCCESS {
    26. c.JSON(http.StatusUnauthorized, gin.H{
    27. "code" : code,
    28. "msg" : e.GetMsg(code),
    29. "data" : data,
    30. })
    31. c.Abort()
    32. return
    33. }
    34. c.Next()
    35. }
    36. }

    如何获取Token

    那么我们如何调用它呢,我们还要获取Token呢?

    1、 我们要新增一个获取Token的API

    models下新建auth.go文件,写入内容:

    1. package models
    2. type Auth struct {
    3. ID int `gorm:"primary_key" json:"id"`
    4. Username string `json:"username"`
    5. Password string `json:"password"`
    6. }
    7. func CheckAuth(username, password string) bool {
    8. var auth Auth
    9. db.Select("id").Where(Auth{Username : username, Password : password}).First(&auth)
    10. if auth.ID > 0 {
    11. return true
    12. }
    13. return false
    14. }

    routers下的api目录新建auth.go文件,写入内容:

    1. package api
    2. import (
    3. "log"
    4. "net/http"
    5. "github.com/gin-gonic/gin"
    6. "github.com/astaxie/beego/validation"
    7. "gin-blog/pkg/e"
    8. "gin-blog/pkg/util"
    9. "gin-blog/models"
    10. )
    11. type auth struct {
    12. Username string `valid:"Required; MaxSize(50)"`
    13. Password string `valid:"Required; MaxSize(50)"`
    14. }
    15. func GetAuth(c *gin.Context) {
    16. username := c.Query("username")
    17. password := c.Query("password")
    18. valid := validation.Validation{}
    19. a := auth{Username: username, Password: password}
    20. ok, _ := valid.Valid(&a)
    21. data := make(map[string]interface{})
    22. code := e.INVALID_PARAMS
    23. if ok {
    24. isExist := models.CheckAuth(username, password)
    25. if isExist {
    26. token, err := util.GenerateToken(username, password)
    27. if err != nil {
    28. code = e.ERROR_AUTH_TOKEN
    29. } else {
    30. data["token"] = token
    31. code = e.SUCCESS
    32. }
    33. } else {
    34. code = e.ERROR_AUTH
    35. }
    36. } else {
    37. for _, err := range valid.Errors {
    38. log.Println(err.Key, err.Message)
    39. }
    40. }
    41. c.JSON(http.StatusOK, gin.H{
    42. "code" : code,
    43. "msg" : e.GetMsg(code),
    44. "data" : data,
    45. })
    46. }

    我们打开routers目录下的router.go文件,修改文件内容(新增获取token的方法):

    1. package routers
    2. import (
    3. "github.com/gin-gonic/gin"
    4. "gin-blog/routers/api"
    5. "gin-blog/routers/api/v1"
    6. "gin-blog/pkg/setting"
    7. )
    8. func InitRouter() *gin.Engine {
    9. r := gin.New()
    10. r.Use(gin.Logger())
    11. r.Use(gin.Recovery())
    12. gin.SetMode(setting.RunMode)
    13. r.GET("/auth", api.GetAuth)
    14. apiv1 := r.Group("/api/v1")
    15. {
    16. ...
    17. }
    18. return r
    19. }

    验证Token

    获取token的API方法就到这里啦,让我们来测试下是否可以正常使用吧!

    重启服务后,用GET方式访问http://127.0.0.1:8000/auth?username=test&password=test123456,查看返回值是否正确

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

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

    将中间件接入Gin

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

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

    1. package routers
    2. import (
    3. "github.com/gin-gonic/gin"
    4. "gin-blog/routers/api"
    5. "gin-blog/routers/api/v1"
    6. "gin-blog/pkg/setting"
    7. "gin-blog/middleware/jwt"
    8. )
    9. func InitRouter() *gin.Engine {
    10. r := gin.New()
    11. r.Use(gin.Logger())
    12. r.Use(gin.Recovery())
    13. gin.SetMode(setting.RunMode)
    14. r.GET("/auth", api.GetAuth)
    15. apiv1 := r.Group("/api/v1")
    16. apiv1.Use(jwt.JWT())
    17. {
    18. ...
    19. }
    20. return r
    21. }

    当前目录结构:

    1. gin-blog/
    2. ├── conf
    3. └── app.ini
    4. ├── main.go
    5. ├── middleware
    6. └── jwt
    7. └── jwt.go
    8. ├── models
    9. ├── article.go
    10. ├── auth.go
    11. ├── models.go
    12. └── tag.go
    13. ├── pkg
    14. ├── e
    15. ├── code.go
    16. └── msg.go
    17. ├── setting
    18. └── setting.go
    19. └── util
    20. ├── jwt.go
    21. └── pagination.go
    22. ├── routers
    23. ├── api
    24. ├── auth.go
    25. └── v1
    26. ├── article.go
    27. └── tag.go
    28. └── router.go
    29. ├── runtime

    到这里,我们的JWT编写就完成啦!

    验证功能

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

    • http://127.0.0.1:8000/api/v1/articles
    • http://127.0.0.1:8000/api/v1/articles?token=23131

    正确的反馈应该是

    1. {
    2. "code": 400,
    3. "data": null,
    4. "msg": "请求参数错误"
    5. }
    6. {
    7. "code": 20001,
    8. "data": null,
    9. "msg": "Token鉴权失败"
    10. }

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

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

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

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

    1. {
    2. "code": 200,
    3. "data": {
    4. "lists": [
    5. {
    6. "id": 2,
    7. "created_on": 1518700920,
    8. "modified_on": 0,
    9. "tag_id": 1,
    10. "tag": {
    11. "id": 1,
    12. "created_on": 1518684200,
    13. "modified_on": 0,
    14. "name": "tag1",
    15. "created_by": "",
    16. "modified_by": "",
    17. "state": 0
    18. },
    19. "content": "test-content",
    20. "created_by": "test-created",
    21. "modified_by": "",
    22. "state": 0
    23. }
    24. ],
    25. "total": 1
    26. },
    27. "msg": "ok"
    28. }

    返回正确,至此我们的jwt-goGin中的验证就完成了!

    参考

    本系列示例代码

    • go-gin-example