源码: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, "服务器错误", "令牌操作错误"}
)
暂无评论