This section is inherited from the previous one. This article will create an account user information table, use bcrypt to implement password encryption, and jwt to implement the login status verification function.
Detailed reference address of this project code https://github.com/jiangbo66666/gin-vue-microBlog
1. Create account information, user information table and use the user_id in the account information table to associate two tables
In order to realize our login function, we need to store user information. Considering the problem that users contain too much information, I decided to use two tables, one account information table and one user information table. 表结构如下
.
//性别类型
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表
}
For detailed function realization, see https://github.com/jiangbo66666/gin-vue-microBlog
And use gorm DB.AutoMigrate(AccountInfo{})
to perform operations on the above account information table, use the AccountInfo userid
field as a foreign key to associate the user information table, and create two empty tables
Note:
First of all, I consider that the account name and mobile phone number may be used for query operations when logging in, so a unique index is added to the account name and mobile phone number to optimize the query speed.
至此我们的表结构就建立完毕。
2. Use bcrypt to encrypt the account password
In the account information table, we need to store the user's account password. However, the password cannot be stored in plain text, and there is a risk of being stolen. So consider using bcrypt
a random salt value to encrypt the password of the account and store it.
First, we execute the following method on the terminal to load the bcrypt package
go get -u golang.org/x/crypto/bcrypt
And introduce it in the project, and create the following two methods: realize the encryption of plaintext passwords, and verify
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
}
For detailed function realization, see https://github.com/jiangbo66666/gin-vue-microBlog
Note:
The above PasswordHash
method accepts a []byte type of plaintext password and returns an encrypted password of string type.
PasswordVerify
The method accepts a plaintext password string and an encrypted password string. The function will compare the two password strings and return a Boolean verification result
至此我们的账号密码的加密解密功能就此实现。
3. Use jwt to verify the login status
The full name of jwt is json web token, which is an identity token, which itself consists of three parts
header
:Token header, which records the type and signature algorithm of the entire token
payload
:The token payload records the saved subject information, and account information can be saved in this project.
signature
:Token signature, the entire token is signed according to the signature algorithm of the header, and the signature can ensure that the token is not forged and tampered
How to use it in code?
First we need to download the jwt package,This package is the latest package, usage may be different from the past, this project code refers to the official document
go get -u github.com/golang-jwt/jwt/v5
Code
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("解析账号名称失败")
}
}
For detailed function realization, see https://github.com/jiangbo66666/gin-vue-microBlog
GenerateToken
Receive a name in the form of a string, that is, the account password as a custom payload, which is used to compare account information when executing a query
VarifyToken
Receive a token in the form of a string, and return an account name in the form of a string and an err, which is used to verify whether the verification failed or not
In the business logic, we return the token of the current account after the login request, the code is as follows
//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": "登陆失败",
})
}
}
For detailed function realization, see https://github.com/jiangbo66666/gin-vue-microBlog
We can perform token verification in routing middleware for scenarios where token verification is required, and resolve account names for information query in this scenario
The routing processing code is as follows
// 路由分组,读取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)
}
For detailed function realization, see https://github.com/jiangbo66666/gin-vue-microBlog