还是先从models/user.go文件开始编写,写入一个构造体JwtClaims:
// JwtClaims用于天生Jwt tokentype JwtClaims struct {UserID uint `json:"user_id"`Username string `json:"username"`jwt.StandardClaims}
以上代码定义了一个名为 JwtClaims 的构造体,该构造体常日用于在 Go 运用程序中天生 JSON Web 令牌 (JWT)
总的来说 JwtClaims 构造体旨在保存天生 JWT 干系的信息。它包括用户身份验证详细信息(UserID 和 Username),也包括来自 jwt 包定义的标准声明。

再写入一个验证密码的函数CheckPassword:
// 验证密码func CheckPassword(hashedPassword, plainPassword string) bool {err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(plainPassword))return err == nil}
CheckPassword 的函数吸收两个字符串参数:
hashedPassword: 存储的哈希密码 (来自数据库)plainPassword: 用户输入的密码 (未加密)函数返回一个布尔值 (bool),表示密码匹配 (true) 或不匹配 (false)。
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(plainPassword))这行代码利用 bcrypt 包的 CompareHashAndPassword 函数进行密码比较。 它将两个切片 ([]byte) 作为参数:
第一个切片 ([]byte(hashedPassword)) 将存储的哈希密码转换为字节切片。第二个切片 ([]byte(plainPassword)) 将用户输入的密码转换为字节切片。CompareHashAndPassword 函数比较两个密码的哈希值之后将其结果存储在 err 变量中。
return err == nil 通过比较 err 是否为 nil 来判断密码是否匹配,并返回相应的布尔值 (true 或 false)。
当有了存储JWT的构造体和验证密码的函数后,我们就可以去写天生用户令牌和用户登录的的代码了。
在utils目录中新建文件jwt.go,并写入如下内容:
package utilsimport ("time""xblog/models""github.com/dgrijalva/jwt-go")var jwtSecret = []byte("my_secret_key")// GenerateToken天生用户令牌func GenerateToken(userID uint, username string) (string, error) {now := time.Now()claims := models.JwtClaims{UserID: userID,Username: username,StandardClaims: jwt.StandardClaims{ExpiresAt: now.Add(time.Hour 24 7).Unix(), // 一周有效期Issuer: "xblog",},}token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)return token.SignedString(jwtSecret)}
此文件目前只包含了一个函数(GenerateToken),也是我要重点先容的。
var jwtSecret = []byte("my_secret_key") 定义了一个名为 jwtSecret 的全局变量,用于存储 JWT 署名利用的密钥。把稳,这是一个示例密钥,请勿将其用于生产环境中。func GenerateToken(userID uint, username string) (string, error) 定义了一个名为 GenerateToken 的函数。函数吸收两个参数:userID: 用户 ID (类型为 uint) username: 用户名 (类型为 string)。该函数返回两个值:天生的 JWT 令牌 (类型为 string) 发生缺点时返回的缺点工具 (类型为 error)now := time.Now(): 获取当前韶光。claims := models.JwtClaims{ ... } 创建一个 models.JwtClaims 构造体的实例 (models.JwtClaims 已经在 xblog/models 包中完成)。设置构造体字段的值: UserID: 利用传入的 userID 参数。Username: 利用传入的 username 参数。StandardClaims: 嵌入 jwt.StandardClaims 构造体,并设置以下字段: ExpiresAt: 令牌的过期韶光为当前韶光加上 7 天 (利用 time.Hour 24 7)。 Issuer: 令牌的颁发者为 "xblog"。token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims): 利用 jwt-go 库创建新的 JWT 令牌。第一个参数指定署名方法为 HMAC SHA-256 ( jwt.SigningMethodHS256 )。第二个参数是之前创建的 claims 构造体。return token.SignedString(jwtSecret): 利用预先定义的 jwtSecret 密钥对令牌进行署名,并返回署名的字符串总的来说 GenerateToken 函数用于天生一个包含用户 ID、用户名、过期韶光和颁发者信息的 JWT 令牌。这个令牌可以用来验证用户身份并付与访问权限。
在utils目录中新建文件auth.go,大概只看文件名你就能想到它的用场。写入如下内容:
package utilsimport ("errors""net/http""strings""xblog/models""github.com/dgrijalva/jwt-go""github.com/gin-gonic/gin""gorm.io/gorm")// Login 用户上岸func Login(username, password string) (user models.User, err error) {user = &models.User{}// 直接查询用户。如果存在,则加载详细信息;如果不存在,则返回缺点信息result := models.DB.Where("username = ?", username).First(user)if errors.Is(result.Error, gorm.ErrRecordNotFound) {return nil, errors.New("用户名或密码缺点") // 返回通用缺点信息} else if result.Error != nil {return nil, result.Error // 返回其它数据库查询缺点}// 验证密码if !models.CheckPassword(user.Password, password) {return nil, errors.New("用户名或密码缺点") // 返回通用缺点信息}return user, nil}// RequireAuth是验证Jwt的中间件func RequireAuth(c gin.Context) {authToken := c.GetHeader("Authorization")if authToken == "" {c.JSON(http.StatusUnauthorized, gin.H{"error": "未供应认证令牌"})c.Abort()return}parts := strings.Fields(authToken)if len(parts) < 2 || parts[0] != "Bearer" {c.JSON(http.StatusUnauthorized, gin.H{"error": "认证令牌格式缺点"})c.Abort()return}tokenStr := parts[1]claims := &models.JwtClaims{}token, err := jwt.ParseWithClaims(tokenStr, claims, func(token jwt.Token) (interface{}, error) {return jwtSecret, nil})if err != nil || !token.Valid {c.JSON(http.StatusUnauthorized, gin.H{"error": "认证令牌无效"})c.Abort()return}// 验证通过后,将用户信息设置到Context中,供后续处理函数利用c.Set("currentUser", claims)c.Next()}
Login 该函数用于用户登录,验证用户名和密码是否精确,并返回用户信息。它吸收两个参数:username: 用户名 password: 密码 (类型是 string)。同样返回两值:user: 登录成功后返回的用户信息 (类型为 models.User) err: 登录失落败时返回的缺点信息 (类型为 error)
函数内部逻辑:
利用 models.DB 查询是否存在指定用户名的用户。如果用户不存在,则返回 errors.New("用户名或密码缺点") 缺点。如果用户存在,则加载用户详细信息。利用 models.CheckPassword 函数验证用户输入的密码是否精确。如果密码缺点,则返回 errors.New("用户名或密码缺点") 缺点。如果验证通过,则返回用户信息和 nil 缺点。RequireAuth 该函数用于验证用户是否携带并拥有有效的 JWT 令牌。它只吸收一个类型为 gin.Context的参数(Gin 框架的高下文)
函数内部逻辑:
从要求头中获取 Authorization 字段的值,即 JWT 令牌。如果 Authorization 字段为空,则返回 http.StatusUnauthorized 状态码和缺点信息 "未供应认证令牌"。将 Authorization 字段的值分割成两部分,并检讨格式是否精确。如果格式禁绝确,则返回 http.StatusUnauthorized 状态码和缺点信息 "认证令牌格式缺点"。利用 jwt.ParseWithClaims 函数解析 JWT 令牌,并将其结果存储在 claims 构造体中。如果解析失落败或令牌无效,则返回 http.StatusUnauthorized 状态码和缺点信息 "认证令牌无效"。如果验证通过,则将用户信息设置到 Gin 高下文中,供后续处理函数利用。总结:这两个函数共同实现了用户认证功能,确保只有拥有有效令牌的用户才能访问须要权限的资源。
接下来便是API接口和路由了。
打开api/user.go文件,编写如下代码:
func UserLogin(c gin.Context) {var u models.Userif err := c.ShouldBindJSON(&u); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})return}user, err := utils.Login(u.Username, u.Password)if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error(),})return}token, err := utils.GenerateToken(user.ID, user.Username)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error(),})return}c.JSON(http.StatusOK, gin.H{"token": token,})}
UserLogin 该函数用于处理用户登录要求。让我们来详细分解一下:
1. var u models.User
声明了一个名变量 u,类型为 models.User。2. if err := c.ShouldBindJSON(&u); err != nil {
考试测验从要求体中解析 JSON 数据并将其绑定到变量 u 上。如果解析过程中涌现缺点 (err != nil),则实行后续代码块。3. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
解析 JSON 数据出错时实行到这里。向客户端返回 JSON 相应,状态码为 http.StatusBadRequest (要求格式缺点)。相应体包含 "error" 键,值为解析缺点信息 err.Error()。4. user, err := utils.Login(u.Username, u.Password)
调用 utils.Login 函数考试测验登任命户,利用 u.Username 和 u.Password 进行验证。登录成功后,user 变量会存储返回的用户信息 (类型为 models.User)。5. if err != nil {
检讨 utils.Login 函数返回的缺点 (err)。如果登录失落败 (err != nil) 实行后续代码块。6. c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
向客户端返回 JSON 相应,状态码为 http.StatusUnauthorized (未授权)。相应体包含 "error" 键,值为登录失落败信息 err.Error()。7. token, err := utils.GenerateToken(user.ID, user.Username)
如果登录成功,调用 utils.GenerateToken 函数天生 JWT 令牌。利用登录成功返回的用户信息 (user.ID 和 user.Username) 作为参数天生令牌。天生的 JWT 令牌会存储在 token 变量中。8. if err != nil {
检讨 utils.GenerateToken 函数返回的缺点 (err)。天生令牌失落败 (err != nil) 时实行后续代码块。9. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
向客户端返回 JSON 相应,状态码为 http.StatusInternalServerError (做事器内部缺点)。相应体包含 "error" 键,值为天生令牌失落败信息 err.Error()。10. c.JSON(http.StatusOK, gin.H{"token": token})
如果登录和天生令牌都成功,利用 c.JSON 方法向客户端返回 JSON 相应。状态码为 http.StatusOK (要求成功)。相应体包含 "token" 键,值为天生的 JWT 令牌 (token)。完成上述代码后保存,如果IDE不能自动添加"xblog/utils",我们在import中手动添加它。
末了一步。在router.go文件(路由)中写入用户登录的路由:
public := r.Group("api/v1"){public.POST("user/add", api.UserAdd)public.POST("user/login", api.UserLogin)}
好,接下来我们利用如下CURL测试一下:
# 测试Logincurl -X POST \-H "Content-Type: application/json" \-d '{"username":"user1","password":"passwd123"}' \http://10.0.0.185:8000/api/v1/user/login; echo
由图片可知,程序成功返回用户登录后的token。