前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Beego学习——Jwt实现用户登录注册

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

作者头像
传说之下的花儿
发布2023-04-16 14:58:20
7560
发布2023-04-16 14:58:20
举报

启动: bee run -gendoc=true -downdoc=true 文章内容中有的注释是个人的理解,可能不严谨 使用 mysql 的话记得导入包: _ "github.com/go-sql-driver/mysql"

1. models包

1.1 jwt.go

代码语言:javascript
复制
// 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

代码语言:javascript
复制
// 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

代码语言:javascript
复制
// 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

代码语言:javascript
复制
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包

代码语言:javascript
复制
appname = 项目名称
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true
EnableDocs = true
sqlconn =

[mysql]
dbHost = "localhost"
dbPort = "3306"
dbUser = "用户名"
dbName = "数据库名称"
dbPassword = "数据库密码"

5. main.go

代码语言:javascript
复制
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)
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-10-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. models包
    • 1.1 jwt.go
      • 1.2 user.go
      • 2. controller包
        • 2.1 user.go
        • 3.routers包
          • 3.1 router.go
          • 4. conf包
          • 5. main.go
          相关产品与服务
          云数据库 MySQL
          腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档