go学习之路(2)——gorm/gin项目实现账号、用户信息表的创建,使用bcrypt密码加密,jwt实现登录状态校验功能(1)

本节承接自上篇,此篇将会创建账号用户信息表、使用bcrypt实现密码加密、jwt实现登录状态校验功能。

    • 1、创建账号信息,用户信息表并用账号信息表中的user_id关联两张表
    • 2、使用bcrypt对账号密码进行加密操作
    • 3、使用jwt实现登录状态的校验

本项目代码详细参考地址 https://github.com/jiangbo66666/gin-vue-microBlog

1、创建账号信息,用户信息表并用账号信息表中的user_id关联两张表

为了实现我们的登录功能,我们需要存储用户的信息,考虑到用户包含信息过多的问题,我决定常见两张表,一张账号信息表,一张用户信息表。表结构如下

//性别类型
type Gender string

const (
	Male   Gender = "男"
	Female Gender = "女"
	other  Gender = "女"
)

// 用户信息表
type UserInfo struct {
	ID          uint      `gorm:"primaryKey;autoIncrement;comment:'用户id';uniqueIndex"` //id主键,自增
	Name        string    `gorm:"comment:'用户姓名'"`
	Sex         Gender    `gorm:"type:enum('男', '女', '其他');comment:'用户性别'"` //创建枚举类型
	BirthDay    time.Time `gorm:"comment:'用户生日'"`
	PhoneNumber string    `gorm:"comment:'用户手机号码'"`
	Email       string    `gorm:"comment:'用户邮箱'"`
	Address     string    `gorm:"comment:'用户地址'"`
	CreateBy    int       `gorm:"comment:'用户由谁创建'"`
	CreateAt    time.Time `gorm:"default:(NOW());comment:'创建账号时间'"` //创建默认时间
	UpdateAt    time.Time `gorm:"comment:'更新时间'"`
	RecentLogin time.Time `gorm:"comment:'最近登陆时间'"`
	HeaderImage string    `gorm:"comment:'头像地址'"`
	Profile     string    `gorm:"comment:'个人简介'"`
}

type AccountInfo struct {
	ID          uint      `gorm:"primaryKey;autoIncrement;comment:'账号id'"` //id主键,自增
	UserId      uint      `gorm:"comment:'用户id'"`
	PhoneNumber string    `gorm:"comment:'用户手机号码';uniqueIndex;type:varchar(20)"` //账号密码表带入手机号码,方便登录流程,减少登录查询
	AccountName string    `gorm:"comment:'账号名';uniqueIndex;type:varchar(20)"`
	Password    string    `gorm:"comment:'账号密码'"`
	CreateAt    time.Time `gorm:"comment:'创建时间'"`
	UpdateAt    time.Time `gorm:"comment:更新时间"`
	RecentLogin time.Time `gorm:"comment:最近登录时间"`
	Status      int       `gorm:"default:0;comment:'用户账号状态'"`
	User        UserInfo  `gorm:"foreignKey:UserId"` //外键关联userInfo表
}

详细功能实现见 https://github.com/jiangbo66666/gin-vue-microBlog

并且使用gorm的DB.AutoMigrate(AccountInfo{})对上述账号信息表执行操作,利用AccountInfo 的userid 字段作为外键关联用户信息表,创建两张空表
注:
首先我考虑到,登录的情况下可能会使用账号名称,手机号码做查询操作,于是对账号名,手机号码增加了唯一索引,优化查询的速度。

	至此我们的表结构就建立完毕。

2、使用bcrypt对账号密码进行加密操作

在账号信息表中我们需要存储用户的账号密码。但是密码不能明文存储,有被盗取的风险。于是考虑使用bcrypt随机盐值加密账号的密码并存储起来。

首先我们在终端执行下述方法加载bcrypt包

go get -u golang.org/x/crypto/bcrypt 

并在项目中引入,并且创建如下两个方法:实现对明文密码的加密,与校验

import "golang.org/x/crypto/bcrypt"

// 密码加密
func PasswordHash(pwd string) (string, error) {
	// GenerateFromPassword 方法对密码进行加密操作
	bytes, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}

	return string(bytes), err
}

// 密码验证
func PasswordVerify(pwd, hash string) bool {
	// CompareHashAndPassword 方法将加密的数据与原始数据进行对比
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(pwd))

	return err == nil
}

详细功能实现见 https://github.com/jiangbo66666/gin-vue-microBlog

注:
上述的PasswordHash方法接受一个明文密码的[]byte类型传入,并且返回一个字符串类型的加密过后的密码。
PasswordVerify方法接受一个明文密码字符串和一个加密后的密码字符串,函数中会将将两哥密码字符串执行对比,返回一个布尔类型校验的结果

至此我们的账号密码的加密解密功能就此实现。

3、使用jwt实现登录状态的校验

jwt全名json web token,是一种身份令牌,他本身由三个部分组成
header令牌头部,记录了整个令牌的类型和签名算法
payload令牌载荷,记录了保存的主体信息,在本项目中可以保存账号信息。
signature令牌签名,按照头部的签名算法对整个令牌进行签名,签名可以保证令牌不被伪造和篡改

如何在代码中使用?
首先我们需要下载jwt包,该包是最新的包,用法可能与过往有所不同,此项目代码参考自官方文档

go get -u github.com/golang-jwt/jwt/v5

代码实现

type MyClaims struct {
	AccountName string `json:"accountName"`
	jwt.RegisteredClaims
}

// 自定义秘钥
var mySecret = []byte("jiangbo")

// 生成token
func GenerateToken(Name string) (string, error) {
	claims := MyClaims{
		Name,
		jwt.RegisteredClaims{
			// 过期时间
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
			//签名
			Issuer: "microBlog",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 自定义秘钥
	tokenStr, err := token.SignedString(mySecret)
	if err == nil {
		return tokenStr, err
	}
	return "", errors.New("出错了")
}

func VarifyToken(tokenStr string) (string, error) {
	token, err := jwt.ParseWithClaims(tokenStr, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
		// 校验秘钥
		return mySecret, nil
	})
	// 校验失败的时候即返回失败
	if err != nil {
		return "", err
	}
	// 解析账号名称
	claims, ok := token.Claims.(*MyClaims)
	if ok {
		return claims.AccountName, nil
	} else {
		return "", errors.New("解析账号名称失败")
	}
}

详细功能实现见 https://github.com/jiangbo66666/gin-vue-microBlog

GenerateToken 接收一个字符串形式的name,即账号密码作为自定义载荷,用于执行查询时的账号信息对比
VarifyToken接收一个字符串形式的token,并返回一个字符串形式的账号名和一个err,err用于校验验证的失败与否

业务逻辑中,我们在登陆请求以后返回当前的账号的令牌,代码如下

//router包
r.POST("/login", api.LoginByName)

//api user包
// 账号名登录路由函数
func LoginByName(c *gin.Context) {
	var loginInfo account_service.LoginInfo
	bindJson(c, &loginInfo)
	//账号名登录校验,并且返回一个token
	token, err := loginInfo.LoginByNameAndToken()
	if err == nil {
		c.JSON(200, gin.H{
			"code": 200,
			"data": gin.H{"token": token},
			"msg":  "登陆成功",
		})
	} else {
		c.JSON(200, gin.H{
			"code": 500,
			"msg":  "登陆失败",
		})
	}
}

详细功能实现见 https://github.com/jiangbo66666/gin-vue-microBlog

我们可以在路由中间件中对需要校验token的场景执行token校验,并且解析账号名,用于该场景下的信息查询

路由处理代码如下

	// 路由分组,读取userdetail handler
	user := r.Group("/api/user")
	{
		user.Use(func(ctx *gin.Context) {
			token := ctx.GetHeader("Token")
			//校验账号信息
			AccountName, err := util.VarifyToken(token)
			if err != nil {
				ctx.JSON(200, gin.H{
					"code": 501,
					"msg":  "登录失效",
				})

				ctx.Abort()
				return
			} else {
				// token校验通过,将token中的账号信息存储起来
				ctx.Set("AccountName", AccountName)
				ctx.Next()
			}
		})
		user.POST("/info", api.UserDetail)
	}

详细功能实现见 https://github.com/jiangbo66666/gin-vue-microBlog

你可能感兴趣的:(golang,gin,学习,后端)