Beego学习——Jwt实现用户登录注册

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: Beego学习——Jwt实现用户登录注册

启动:

bee run -gendoc=true -downdoc=true

文章内容中有的注释是个人的理解,可能不严谨

使用 mysql 的话记得导入包:

_ "github.com/go-sql-driver/mysql"

1. models包

1.1 jwt.go

// JWT : header payload signature
// json web token: 标头 有效负载 签名
const (
  SecretKEY              string = "JWT-Secret-Key"
  DEFAULT_EXPIRE_SECONDS int    = 600 // 默认10分钟
  PasswordHashBytes             = 16
)
// MyCustomClaims
// This struct is the payload
// 此结构是有效负载
type MyCustomClaims struct {
  UserID int `json:"userID"`
  jwt.StandardClaims
}
// JwtPayload
// This struct is the parsing of token payload
// 此结构是对token有效负载的解析
type JwtPayload struct {
  Username  string `json:"username"`
  UserID    int    `json:"userID"`
  IssuedAt  int64  `json:"iat"` // 发布日期
  ExpiresAt int64  `json:"exp"` // 过期时间
}
// GenerateToken
// @Title GenerateToken
// @Description "生成token"
// @Param loginInfo     *models.LoginRequest  "登录请求"
// @Param userID      int           "用户ID"
// @Param expiredSeconds  int           "过期时间"
// @return    tokenString   string          "编码后的token"
// @return    err       error           "错误信息"
func GenerateToken(loginInfo *LoginRequest, userID int, expiredSeconds int) (tokenString string, err error) {
  // 如果没设置过期时间,默认为 DEFAULT_EXPIRE_SECONDS 600s
  if expiredSeconds == 0 {
    expiredSeconds = DEFAULT_EXPIRE_SECONDS
  }
  // 创建声明
  mySigningKey := []byte(SecretKEY)
  // 过期时间 = 当前时间(/s)+ expiredSeconds(/s)
  expireAt := time.Now().Add(time.Second * time.Duration(expiredSeconds)).Unix()
  logs.Info("Token 将到期于:", time.Unix(expireAt, 0))
  user := *loginInfo
  claims := MyCustomClaims{
    userID,
    jwt.StandardClaims{
      Issuer:    user.Username,   // 发行者
      IssuedAt:  time.Now().Unix(), // 发布时间
      ExpiresAt: expireAt,      // 过期时间
    },
  }
  // 利用上面创建的声明 生成token
  // NewWithClaims(签名算法 SigningMethod, 声明 Claims) *Token
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  // 利用密钥对token签名
  tokenStr, err := token.SignedString(mySigningKey)
  if err != nil {
    return "",
    errors.New("错误: token生成失败!")
  }
  return tokenStr, nil
}
// ValidateToken
// @Title ValidateToken
// @Description "验证token"
// @Param tokenString     string  "编码后的token"
// @return    *JwtPayload     "Jwt有效负载的解析"
// @return    error           "错误信息"
func ValidateToken(tokenString string) (*JwtPayload, error) {
  // 获取编码前的token信息
  token, err := jwt.ParseWithClaims(tokenString,
    &MyCustomClaims{},
    func(token *jwt.Token) (interface{}, error) {
      return []byte(SecretKEY), nil
    })
  // 获取payload-声明内容
  claims, ok := token.Claims.(*MyCustomClaims)
  if ok && token.Valid {
    logs.Info("%v %v",
      claims.UserID,
      claims.StandardClaims.ExpiresAt, // 过期时间
    )
    logs.Info("Token 将过期于:",
      time.Unix(claims.StandardClaims.ExpiresAt, 0),
    )
    return &JwtPayload{
      Username:  claims.StandardClaims.Issuer,  // 用户名:发行者
      UserID:    claims.UserID,
      IssuedAt:  claims.StandardClaims.IssuedAt,
      ExpiresAt: claims.StandardClaims.ExpiresAt,
    }, nil
  } else {
    logs.Info(err.Error())
    return nil, errors.New("错误: token验证失败")
  }
}
// RefreshToken
// @Title RefreshToken
// @Description "更新token"
// @Param tokenString     string    "编码后的token"
// @return   newTokenString string    "编码后的新的token"
// @return   err        error     "错误信息"
func RefreshToken(tokenString string) (newTokenString string, err error) {
  // 获取上一个token
  token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{},
    func(token *jwt.Token) (interface{}, error) {
      return []byte(SecretKEY), nil
    })
  // 获取上一个token 的 payload-声明
  claims, ok := token.Claims.(*MyCustomClaims)
  if !ok || !token.Valid {
    return "", err
  }
  // 创建新的声明
  mySigningKey := []byte(SecretKEY)
  expireAt := time.Now().Add(time.Second * time.Duration(DEFAULT_EXPIRE_SECONDS)).Unix() //new expired
  newClaims := MyCustomClaims{
    claims.UserID,
    jwt.StandardClaims{
      Issuer:    claims.StandardClaims.Issuer, //name of token issue
      IssuedAt:  time.Now().Unix(),            //time of token issue
      ExpiresAt: expireAt,
    },
  }
  // 利用新的声明,生成新的token
  newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims)
  // 利用签名算法对新的token进行签名
  tokenStr, err := newToken.SignedString(mySigningKey)
  if err != nil {
  return "", errors.New("错误: 新的新json web token 生成失败!")
  }
  return tokenStr, nil
}
// GenerateSalt
// @Title GenerateSalt
// @Description "生成用户的加密的钥匙|generate salt"
// @return   salt     string    "生成用户的加密的钥匙"
// @return   err      error     "错误信息"
func GenerateSalt() (salt string, err error) {
  buf := make([]byte, PasswordHashBytes)
  if _, err := io.ReadFull(rand.Reader, buf); err != nil {
    return "", errors.New("error: failed to generate user's salt")
  }
  return fmt.Sprintf("%x", buf), nil
}
// GeneratePassHash
// @Title GenerateSalt
// @Description "对密码加密|generate password hash"
// @Param password    string    "用户登录密码"
// @Param salt      string    "用户的加密的钥匙"
// @return    hash   string         "加密后的密码"
// @return    err    error          "错误信息"
func GeneratePassHash(password string, salt string) (hash string, err error) {
  h, err := scrypt.Key([]byte(password), []byte(salt), 16384, 8, 1, PasswordHashBytes)
  if err != nil {
    return "", errors.New("error: failed to generate password hash")
  }
  return fmt.Sprintf("%x", h), nil
}

1.2 user.go

// TabUser 定义用户格式
type TabUser struct {
  Id            int    `json:"id" orm:"column(id);auto"`
  UserName      string `json:"username" orm:"column(username);size(128)"`
  Password      string `json:"password" orm:"column(password);size(128)"`
  Salt          string `json:"salt" orm:"column(salt);size(128)"`
}
// LoginRequest 定义登录请求格式
type LoginRequest struct {
  Username string `json:"username"`
  Password string `json:"password"`
}
// LoginResponse 定义登录响应
type LoginResponse struct {
  Username    string             `json:"username"`
  UserID      int                `json:"userID"`
  Token       string             `json:"token"`
}
//CreateRequest 定义创建用户请求格式
type CreateRequest struct {
  Username string `json:"username"`
  Password string `json:"password"`
}
//CreateResponse 定义创建用户响应
type CreateResponse struct {
  UserID   int    `json:"userID"`
  Username string `json:"username"`
}
// DoLogin
// @Title DoLogin
// @Description "用户登录"
// @Param lr    *LoginRequest   "登录请求"
// @return    *LoginResponse        "登录响应"
// @return    int             "状态码"
// @return    error             "错误信息"
func DoLogin(lr *LoginRequest) (*LoginResponse, int, error) {
  // 获取用户名和密码
  username := lr.Username
  password := lr.Password
  // 验证用户名和密码是否为空
  if len(username) == 0 || len(password) == 0 {
    return nil,
      http.StatusBadRequest,
      errors.New("error: 用户名或密码为空")
  }
  // 连接数据库
  o := orm.NewOrm()
  // 检查用户名是否存在
  user := &TabUser{UserName: username}
  err := o.Read(user, "username")
  if err != nil {
    return nil,
      http.StatusBadRequest,  // 400
      errors.New("error: 用户名不存在")
  }
  // 生成hash加密后的密码
  hash, err := GeneratePassHash(password, user.Salt)
  if err != nil {
    return nil,
      http.StatusBadRequest, // 400
      err
  }
  // 比较用户输入的密码+用户的加密钥匙生成的hash密码 与 数据库中存的hash密码
  if hash != user.Password {
    return nil,
      http.StatusBadRequest,
      errors.New("错误: 密码错误!")
  }
  // 生成token
  tokenString, err := GenerateToken(lr, user.Id, 0)
  if err != nil {
    return nil,
      http.StatusBadRequest,
      err
  }
  // 生成的token 返回给前端
  return &LoginResponse{
    Username:    user.UserName,
    UserID:      user.Id,
    Token:       tokenString,
  }, http.StatusOK, nil
}
// DoCreateUser
// @Title DoCreateUser
// @Description "创建用户"
// @Param  cr   *CreateRequest    "用户创建请求"
// @return    *CreateResponse       "用户创建响应"
// @return    int             "状态码"
// @return    error             "错误信息"
func DoCreateUser(cr *CreateRequest) (*CreateResponse, int, error) {
  // 连接数据库
  o := orm.NewOrm()
  // 检查用户名是否存在
  userNameCheck := TabUser{UserName: cr.Username}
  err := o.Read(&userNameCheck, "username")
  if err == nil {
    return nil, http.StatusBadRequest, errors.New("username has already existed")
  }
  // 生成 用户的加密的钥匙
  saltKey, err := GenerateSalt()
  if err != nil {
    logs.Info(err.Error())
    return nil, http.StatusBadRequest, err
  }
  // 生成hash加密的密码
  hash, err := GeneratePassHash(cr.Password, saltKey)
  if err != nil {
    logs.Info(err.Error())
    return nil, http.StatusBadRequest, err
  }
  // 创建用户
  user := TabUser{}
  user.UserName = cr.Username
  user.Password = hash
  user.Salt = saltKey
  _, err = o.Insert(&user)
  if err != nil {
    logs.Info(err.Error())
    return nil, http.StatusBadRequest, err
  }
  return &CreateResponse{
    UserID:   user.Id,
    Username: user.UserName,
  }, http.StatusOK, nil
}
// 映射mysql数据
func init() {
  orm.RegisterModel(new(TabUser))
}

2. controller包

2.1 user.go

// UserController 处理与用户相关的请求
// user API
type UserController struct {
  beego.Controller
}
// 解析请求,并将请求体存储到v中
// unmarshalPayload
// @Param v interface{} true  "接收解析后的请求体的变量"
func (c *UserController) unmarshalPayload(v interface{}) error {
  // json 解析
  // Unmarshal(data []byte, v interface{})
  // 将json字符串解码到相应的数据结构
  err := json.Unmarshal(c.Ctx.Input.RequestBody, &v)
  if err != nil {
    logs.Error("RequestBody 解析失败!")
  }
  if err != nil {
    logs.Error("unmarshal payload of %s error: %s", c.Ctx.Request.URL.Path, err)
  }
  return nil
}
// respond
// @Title respond
// @Description 返回给前端的信息
// @Param code      int       true    "状态码"
// @Param message     string      true    "返回信息"
// @Param data      ...interface{}  true    "数据"
func (c *UserController) respond(code int, message string, data ...interface{}) {
  c.Ctx.Output.SetStatus(code)
  var d interface{}
  if len(data) > 0 {
    d = data[0]
  }
  c.Data["json"] = struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
  }{
    Code:    code,
    Message: message,
    Data:    d,
  }
  c.ServeJSON()
}
// Login
// @Title Login
// @Description 处理登录请求
func (c *UserController) Login() {
  lr := new(models.LoginRequest)
  if err := c.unmarshalPayload(lr); err != nil {
    c.respond(http.StatusBadRequest, err.Error())
    return
  }
  lrs, statusCode, err := models.DoLogin(lr)
  if err != nil {
    c.respond(statusCode, err.Error())
    return
  }
  // 将token设置到Header
  c.Ctx.Output.Header("Authorization", lrs.Token)
  c.respond(http.StatusOK, "", lrs)
}
// CreateUser
// @Title CreateUser
// @Description 新增用户
// @Success 200
// @router /register [post]
func (c *UserController) CreateUser() {
  cu := new(models.CreateRequest)
  // 获取request body
  if err := c.unmarshalPayload(cu); err != nil {
    c.respond(http.StatusBadRequest, err.Error())
  }
  createUser, statusCode, err := models.DoCreateUser(cu)
  if err != nil {
    c.respond(statusCode, err.Error())
    return
  }
  c.respond(http.StatusOK, "", createUser)
}

3.routers包

3.1 router.go

func init() {
  ns := beego.NewNamespace("/api",
    beego.NSNamespace("/user",
      beego.NSRouter("/login", &controllers.UserController{}, "post:Login"),
      beego.NSRouter("/register", &controllers.UserController{}, "post:CreateUser"),
    ),
  )
  beego.AddNamespace(ns)
}

4. conf包

appname = 项目名称
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true
EnableDocs = true
sqlconn =
[mysql]
dbHost = "localhost"
dbPort = "3306"
dbUser = "用户名"
dbName = "数据库名称"
dbPassword = "数据库密码"

5. main.go

func main() {
  if beego.BConfig.RunMode == "dev" {
    beego.BConfig.WebConfig.DirectoryIndex = true
    beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
  }
  beego.Run()
}
func init() {
  // mysql
  dbHost := beego.AppConfig.String("dbHost")
  dbPort := beego.AppConfig.String("dbPort")
  dbUser := beego.AppConfig.String("dbUser")
  dbPassword := beego.AppConfig.String("dbPassword")
  dbName :=beego.AppConfig.String("dbName")
  dsn := dbUser + ":" + dbPassword +"@tcp("+ dbHost +":"+ dbPort +")/"+ dbName +"?charset=utf8"
  // 设置数据库基本信息,相当于连接数据库
  _ = orm.RegisterDataBase("default","mysql",dsn,30)
  // 生成表
  _ = orm.RunSyncdb("default", false, true)
}
相关文章
|
4月前
|
Java 测试技术 数据安全/隐私保护
SpringCloud微服务之最全JWT学习教程03
SpringCloud微服务之最全JWT学习教程03
89 0
|
8月前
|
JSON 前端开发 数据库
学习nest.js中如何使用jwt进行身份验证,这一篇文章就够
学习nest.js中如何使用jwt进行身份验证,这一篇文章就够
|
3月前
SpringBoot_JWT用户登录
SpringBoot_JWT用户登录
33 0
|
9月前
|
开发框架 .NET API
10分钟简单学习net core集成jwt权限认证,快速接入项目落地使用 (下)
10分钟简单学习net core集成jwt权限认证,快速接入项目落地使用 (下)
|
9月前
|
存储 JSON 开发框架
10分钟简单学习net core集成jwt权限认证,快速接入项目落地使用 (上)
10分钟简单学习net core集成jwt权限认证,快速接入项目落地使用
|
11月前
|
XML 存储 SQL
通过webgoat-xxe、jwt学习Java代码审计
通过webgoat-xxe、jwt学习Java代码审计
288 0
jira学习案例26-用useHttp管理jwt和登录状态
jira学习案例26-用useHttp管理jwt和登录状态
53 0
jira学习案例26-用useHttp管理jwt和登录状态
jira学习案例22-jwt原理-auth-provider
jira学习案例22-jwt原理-auth-provider
48 0
jira学习案例22-jwt原理-auth-provider
|
XML 存储 JSON
gin框架学习-JWT认证
由于 JSON 不像 XML 那样冗长,因此在对其进行编码时,它的大小也更小,这使得 JWT 比 SAML 更紧凑。这使得 JWT 成为在 HTML 和 HTTP 环境中传递的不错选择。
212 0
gin框架学习-JWT认证
|
20天前
|
安全 数据安全/隐私保护
Springboot+Spring security +jwt认证+动态授权
Springboot+Spring security +jwt认证+动态授权