(三) beego开发web应用 – JWT

源码:https://github.com/Nicolana/hello-bee/tree/main

Jwt 是啥

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。

原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

{
  "姓名": "张三",
  "角色": "管理员",
  "到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

JWT数据结构

jwt依次由三部分组成:

Header(头部)
Payload(负载)
Signature(签名)

写成一行,就是下面的样子。

Header.Payload.Signature

中间用 . (点)隔开

具体参考:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

设置密钥

根据上面的知识,我们需要设置一个密钥来加密生成签名

conf/app.conf 中添加一行

token = gHwTyfKUgbzWEU3LoLrptRxMdmv1Cp

编写Token生成函数

utils/jwt.go

package utils

import (
   "errors"
   beego "github.com/beego/beego/v2/server/web"
   "github.com/dgrijalva/jwt-go"
   "log"
)

type EasyToken struct {
   Username string
   Uid int64
   Expires int64
}

var (
   verifyKey string
   ErrAbsent = "token absent" // 令牌不存在
   ErrInvalid = "token invalid" // 令牌无效
   ErrExpired = "token expired" // 令牌过期
   ErrOther = "other error" // 其他错误
)

func init() {
   verifyKey, _ = beego.AppConfig.String("token")
}

// 生成Token
func (e EasyToken) GetToken() (string, error) {
   claims := &jwt.StandardClaims{
      ExpiresAt: e.Expires,
      Issuer: e.Username,
   }
   token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
   // 感觉这一步是用私钥去生成完整的Token
   tokenString, err := token.SignedString([]byte(verifyKey))
   if err != nil {
      log.Println(err)
   }
   return tokenString, err
}

// 验证Token
func (e EasyToken) ValidateToken(tokenString string) (bool, error) {
   if tokenString == "" {
      return false, errors.New(ErrAbsent)
   }
   token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
      return []byte(verifyKey), nil
   })
   if token == nil {
      return false, errors.New(ErrInvalid)
   }
   if token.Valid {
      return true, nil
   } else if ve, ok := err.(*jwt.ValidationError); ok {
      if ve.Errors&jwt.ValidationErrorMalformed != 0 {
         return false, errors.New(ErrInvalid)
      } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
         return false, errors.New(ErrExpired)
      } else {
         return false, errors.New(ErrOther)
      }
   } else {
      return false, errors.New(ErrOther)
   }
}
  • GetToken 函数是用来生成Token的
  • ValidateToken 方法则是检查Token是否有效

修改Login Controller

controllers/user.go

func (this *UserController) Login() {
   var reqData struct {
      Username string `valid:"Required"`
      Password string `valid:"Required"`
   }
   var token string
   if err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData); err == nil {
      if ok, user := models.Login(reqData.Username, reqData.Password); ok {
         et := utils.EasyToken{}
         validation, err := et.ValidateToken(user.Token)
         if !validation {
            et = utils.EasyToken{
               Username: user.Username,
               Uid: int64(user.Id),
               Expires: time.Now().Unix() + 2 * 3600,
            }
            token, err = et.GetToken()
            fmt.Printf("Token = %s\n", token)
            if token == "" || err != nil {
               this.Data["json"] = errUserToken
               this.ServeJSON()
            } else {
               models.UpdateUserToken(user, token)
            }
         } else {
            token = user.Token
         }
         models.UpdateUserLastLogin(user)
         var returnData = &UserSuccessLoginData{token, user.Username}
         this.Data["json"] = &Response{0, 0, "ok", returnData}
      }
   } else {
      this.Data["json"] = "no user or password"
   }
   this.ServeJSON()
}

其中多了一个涉及Token的错误信息:errUserToken 我们在common.go中进行定义

var (
   errUserToken = &Response{500, 10002, "服务器错误", "令牌操作错误"}
)

测试

如何使用Token?

使用React写一个简单的登录页面

暂无评论

发表评论

您的电子邮件地址不会被公开,必填项已用*标注。

相关推荐