授课语音

开发用户注册、登录与身份验证功能

在构建现代 Web 应用程序时,用户身份验证和授权是至关重要的功能。本文将详细讲解如何通过 Go 架构实现用户注册、登录以及身份验证的功能。我们将使用 JWT(JSON Web Token)作为身份验证的方式,确保用户数据的安全性。


1. 用户注册功能

用户注册是 Web 应用中的基本功能之一,通常需要接受用户输入的用户名、邮箱、密码等信息,进行合法性验证,并将数据存储到数据库中。

1.1 注册接口设计

用户在提交注册信息后,我们需要验证数据的合法性,密码加密存储,并返回注册成功的消息。

package main

import (
	"fmt"
	"log"
	"net/http"
	"github.com/gin-gonic/gin"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// User 定义用户表结构
type User struct {
	ID       uint   `json:"id" gorm:"primaryKey"`
	Username string `json:"username" gorm:"unique"`
	Email    string `json:"email" gorm:"unique"`
	Password string `json:"password"`
}

var db *gorm.DB

func init() {
	// 初始化数据库连接
	var err error
	db, err = gorm.Open(sqlite.Open("user.db"), &gorm.Config{})
	if err != nil {
		log.Fatal("数据库连接失败:", err)
	}
	db.AutoMigrate(&User{}) // 自动迁移,创建表
}

func main() {
	r := gin.Default()

	// 注册路由
	r.POST("/register", register)

	// 启动服务器
	r.Run(":8080")
}

// 注册用户
func register(c *gin.Context) {
	var user User

	// 解析请求中的 JSON 数据
	if err := c.ShouldBindJSON(&user); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
		return
	}

	// 密码加密
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"})
		return
	}
	user.Password = string(hashedPassword)

	// 存储用户信息到数据库
	if err := db.Create(&user).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败"})
		return
	}

	// 返回成功响应
	c.JSON(http.StatusOK, gin.H{"message": "注册成功"})
}

解释

  • 使用 bcrypt 对用户的密码进行加密,确保密码安全。
  • 使用 gorm 进行数据库操作,将用户信息保存到 SQLite 数据库中。
  • gin 框架用于快速处理 HTTP 请求,定义了一个 POST 路由 /register 用于用户注册。

2. 用户登录功能

用户登录时,我们需要验证用户输入的用户名和密码是否正确。成功验证后,返回一个 JWT(JSON Web Token)令牌,该令牌将用于后续的身份验证。

2.1 登录接口设计

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
	"github.com/gin-gonic/gin"
	"github.com/dgrijalva/jwt-go"
	"golang.org/x/crypto/bcrypt"
)

// 用户登录时生成 JWT Token
func login(c *gin.Context) {
	var userInput User

	// 解析请求中的 JSON 数据
	if err := c.ShouldBindJSON(&userInput); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
		return
	}

	var user User
	// 查询数据库中是否存在该用户
	if err := db.Where("username = ?", userInput.Username).First(&user).Error; err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
		return
	}

	// 验证密码是否匹配
	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(userInput.Password)); err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
		return
	}

	// 生成 JWT Token
	token := generateToken(user.ID)

	// 返回 JWT Token
	c.JSON(http.StatusOK, gin.H{"token": token})
}

// 生成 JWT Token
func generateToken(userID uint) string {
	// 定义密钥
	secretKey := []byte("secret")

	// 创建 JWT Token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"user_id": userID,
		"exp":     time.Now().Add(time.Hour * 24).Unix(), // 令牌有效期为 24 小时
	})

	// 签名生成令牌
	tokenString, err := token.SignedString(secretKey)
	if err != nil {
		log.Fatal("生成 Token 失败:", err)
	}

	return tokenString
}

解释

  • 用户输入的用户名和密码会与数据库中的记录进行匹配。
  • 使用 bcrypt.CompareHashAndPassword 比较用户输入的密码和存储的加密密码。
  • 登录成功后,调用 generateToken 生成一个 JWT,并返回给客户端。
  • JWT 包含用户的 user_idexp(过期时间),并使用密钥进行签名。

3. 身份验证功能

在用户登录并获得 JWT 后,后续的请求都需要附带该令牌进行身份验证。我们需要在每个请求中验证 JWT 是否有效。

3.1 中间件:验证 JWT

// 身份验证中间件
func authorizeJWT() gin.HandlerFunc {
	return func(c *gin.Context) {
		tokenString := c.GetHeader("Authorization")

		// 如果没有提供 token
		if tokenString == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "token 不能为空"})
			c.Abort()
			return
		}

		// 解析 Token
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			// 验证 token 签名方法
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("签名方法不正确")
			}
			return []byte("secret"), nil
		})

		// 验证 token 是否有效
		if err != nil || !token.Valid {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的 token"})
			c.Abort()
			return
		}

		// 将用户 ID 存储到上下文中
		claims := token.Claims.(jwt.MapClaims)
		userID := claims["user_id"].(float64)
		c.Set("user_id", userID)

		c.Next()
	}
}

解释

  • 中间件 authorizeJWT 会从请求头中提取 Authorization 字段。
  • 使用 jwt.Parse 来解析并验证 token 是否有效。
  • 如果 token 无效,则返回 401 Unauthorized 错误,否则将用户 ID 存储到请求上下文中,供后续操作使用。

3.2 使用中间件保护路由

r := gin.Default()

// 注册与登录路由
r.POST("/register", register)
r.POST("/login", login)

// 需要身份验证的路由
r.GET("/profile", authorizeJWT(), func(c *gin.Context) {
	userID, _ := c.Get("user_id")
	c.JSON(http.StatusOK, gin.H{"user_id": userID})
})

r.Run(":8080")

解释

  • r.GET("/profile", authorizeJWT(), ...) 表示该路由需要身份验证。只有携带有效的 JWT 才能访问此路由。

4. 总结

通过本课件,我们学习了如何使用 Go 实现用户注册、登录和身份验证功能。具体包括:

  1. 用户注册:用户通过提交数据进行注册,密码加密存储。
  2. 用户登录:用户通过用户名和密码登录,生成 JWT 令牌。
  3. 身份验证:通过 JWT 对后续请求进行身份验证,确保请求的合法性。

这些功能是构建安全的 Web 应用程序所必不可少的基础功能,掌握它们将帮助你实现更高效和安全的用户管理。

去1:1私密咨询

系列课程: