用户认证
本章节将实现用户注册、登录和JWT认证功能,包括密码加密、token生成和权限管理。
📋 学习目标
完成本章节后,你将能够:
- 实现用户注册功能
- 实现用户登录功能
- 使用JWT进行身份认证
- 实现密码加密和验证
- 创建认证中间件
- 实现权限控制
🔐 JWT 认证实现
1. JWT 工具包
创建 pkg/auth/jwt.go:
go
package auth
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
var jwtSecret []byte
type Claims struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func InitJWT(secret string) {
jwtSecret = []byte(secret)
}
// GenerateToken 生成JWT token
func GenerateToken(userID uint, username, role string, expireDuration time.Duration) (string, error) {
claims := Claims{
UserID: userID,
Username: username,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expireDuration)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "blog-system",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
// ParseToken 解析JWT token
func ParseToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("无效的签名方法")
}
return jwtSecret, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("无效的token")
}2. 密码加密
创建 pkg/utils/password.go:
go
package utils
import (
"golang.org/x/crypto/bcrypt"
)
// HashPassword 加密密码
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
// CheckPassword 验证密码
func CheckPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}👤 用户服务
创建 internal/service/user.go:
go
package service
import (
"errors"
"blog-system/internal/model"
"blog-system/internal/repository"
"blog-system/pkg/auth"
"blog-system/pkg/utils"
"time"
)
type UserService interface {
Register(req RegisterRequest) (*model.User, error)
Login(req LoginRequest) (string, error)
GetProfile(userID uint) (*model.User, error)
UpdateProfile(userID uint, req UpdateProfileRequest) error
}
type UserServiceImpl struct {
userRepo repository.UserRepository
}
func NewUserService(userRepo repository.UserRepository) UserService {
return &UserServiceImpl{userRepo: userRepo}
}
type RegisterRequest struct {
Username string `json:"username" binding:"required,min=3,max=50"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=6"`
Nickname string `json:"nickname"`
}
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
type UpdateProfileRequest struct {
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
}
func (s *UserServiceImpl) Register(req RegisterRequest) (*model.User, error) {
// 检查用户名是否存在
if s.userRepo.ExistsByUsername(req.Username) {
return nil, errors.New("用户名已存在")
}
// 检查邮箱是否存在
if s.userRepo.ExistsByEmail(req.Email) {
return nil, errors.New("邮箱已存在")
}
// 加密密码
hashedPassword, err := utils.HashPassword(req.Password)
if err != nil {
return nil, errors.New("密码加密失败")
}
// 创建用户
user := &model.User{
Username: req.Username,
Email: req.Email,
Password: hashedPassword,
Nickname: req.Nickname,
Role: "user",
Status: "active",
}
if err := s.userRepo.Create(user); err != nil {
return nil, err
}
return user, nil
}
func (s *UserServiceImpl) Login(req LoginRequest) (string, error) {
// 查找用户
user, err := s.userRepo.FindByUsername(req.Username)
if err != nil {
return "", errors.New("用户名或密码错误")
}
// 验证密码
if !utils.CheckPassword(req.Password, user.Password) {
return "", errors.New("用户名或密码错误")
}
// 检查用户状态
if user.Status != "active" {
return "", errors.New("用户已被禁用")
}
// 生成token
token, err := auth.GenerateToken(user.ID, user.Username, user.Role, 24*time.Hour)
if err != nil {
return "", errors.New("生成token失败")
}
return token, nil
}
func (s *UserServiceImpl) GetProfile(userID uint) (*model.User, error) {
return s.userRepo.FindByID(userID)
}
func (s *UserServiceImpl) UpdateProfile(userID uint, req UpdateProfileRequest) error {
user, err := s.userRepo.FindByID(userID)
if err != nil {
return err
}
if req.Nickname != "" {
user.Nickname = req.Nickname
}
if req.Avatar != "" {
user.Avatar = req.Avatar
}
return s.userRepo.Update(user)
}🛡️ 认证中间件
创建 pkg/auth/middleware.go:
go
package auth
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "未提供认证token",
})
c.Abort()
return
}
// 移除 "Bearer " 前缀
if len(token) > 7 && strings.HasPrefix(token, "Bearer ") {
token = token[7:]
}
claims, err := ParseToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无效的token",
})
c.Abort()
return
}
// 将用户信息存储到上下文
c.Set("user_id", claims.UserID)
c.Set("username", claims.Username)
c.Set("role", claims.Role)
c.Next()
}
}
func AdminOnly() gin.HandlerFunc {
return func(c *gin.Context) {
role, exists := c.Get("role")
if !exists || role != "admin" {
c.JSON(http.StatusForbidden, gin.H{
"success": false,
"message": "权限不足",
})
c.Abort()
return
}
c.Next()
}
}📝 用户处理器
创建 internal/handler/user.go:
go
package handler
import (
"net/http"
"strconv"
"blog-system/internal/service"
"github.com/gin-gonic/gin"
)
type UserHandler struct {
userService service.UserService
}
func NewUserHandler(userService service.UserService) *UserHandler {
return &UserHandler{userService: userService}
}
func (h *UserHandler) Register(c *gin.Context) {
var req service.RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "请求参数无效",
"error": err.Error(),
})
return
}
user, err := h.userService.Register(req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": err.Error(),
})
return
}
c.JSON(http.StatusCreated, gin.H{
"success": true,
"message": "注册成功",
"data": gin.H{
"id": user.ID,
"username": user.Username,
"email": user.Email,
"nickname": user.Nickname,
},
})
}
func (h *UserHandler) Login(c *gin.Context) {
var req service.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "请求参数无效",
})
return
}
token, err := h.userService.Login(req)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "登录成功",
"data": gin.H{
"token": token,
},
})
}
func (h *UserHandler) GetProfile(c *gin.Context) {
userID, _ := c.Get("user_id")
user, err := h.userService.GetProfile(userID.(uint))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"message": "用户不存在",
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": user,
})
}🔧 路由设置
在 cmd/server/main.go 中添加路由:
go
func setupRoutes(r *gin.Engine, db *gorm.DB) {
// 初始化服务
userRepo := repository.NewUserRepository(db)
userService := service.NewUserService(userRepo)
userHandler := handler.NewUserHandler(userService)
// 初始化JWT
auth.InitJWT(cfg.JWT.Secret)
// 公开路由
api := r.Group("/api")
{
api.POST("/users/register", userHandler.Register)
api.POST("/users/login", userHandler.Login)
}
// 需要认证的路由
authGroup := api.Group("")
authGroup.Use(auth.AuthMiddleware())
{
authGroup.GET("/users/profile", userHandler.GetProfile)
}
}📝 API 使用示例
用户注册
bash
curl -X POST http://localhost:8080/api/users/register \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"email": "test@example.com",
"password": "password123",
"nickname": "测试用户"
}'用户登录
bash
curl -X POST http://localhost:8080/api/users/login \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"password": "password123"
}'获取用户信息
bash
curl http://localhost:8080/api/users/profile \
-H "Authorization: Bearer <token>"⏭️ 下一步
用户认证完成后,下一步是:
- 文章管理 - 实现文章的CRUD操作
🎉 用户认证完成! 现在你可以开始实现文章管理功能了。
