用户认证系统 #

一、认证系统概述 #

1.1 功能模块 #

text
┌─────────────────────────────────────────────────────────┐
│                    认证系统功能                          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  用户注册                                                │
│  ├── 邮箱验证                                           │
│  ├── 密码加密                                           │
│  └── 用户创建                                           │
│                                                         │
│  用户登录                                                │
│  ├── 凭证验证                                           │
│  ├── Token生成                                          │
│  └── Token刷新                                          │
│                                                         │
│  权限控制                                                │
│  ├── 角色管理                                           │
│  └── 权限验证                                           │
│                                                         │
└─────────────────────────────────────────────────────────┘

二、用户模型 #

2.1 模型定义 #

go
package models

import (
    "time"
    "gorm.io/gorm"
)

type User struct {
    ID            uint           `gorm:"primaryKey" json:"id"`
    Name          string         `gorm:"size:100;not null" json:"name"`
    Email         string         `gorm:"size:100;uniqueIndex;not null" json:"email"`
    Password      string         `gorm:"size:255;not null" json:"-"`
    Role          string         `gorm:"size:20;default:'user'" json:"role"`
    Status        string         `gorm:"size:20;default:'active'" json:"status"`
    EmailVerified bool           `gorm:"default:false" json:"email_verified"`
    LastLoginAt   *time.Time     `json:"last_login_at"`
    CreatedAt     time.Time      `json:"created_at"`
    UpdatedAt     time.Time      `json:"updated_at"`
    DeletedAt     gorm.DeletedAt `gorm:"index" json:"-"`
}

func (User) TableName() string {
    return "users"
}

2.2 角色常量 #

go
const (
    RoleAdmin = "admin"
    RoleUser  = "user"
)

const (
    StatusActive   = "active"
    StatusInactive = "inactive"
    StatusBanned   = "banned"
)

三、JWT工具 #

3.1 JWT配置 #

go
package auth

import (
    "errors"
    "time"
    "github.com/golang-jwt/jwt/v5"
)

type JWTConfig struct {
    Secret     string
    Expire     time.Duration
    RefreshExpire time.Duration
}

type Claims struct {
    UserID uint   `json:"user_id"`
    Email string `json:"email"`
    Role  string `json:"role"`
    jwt.RegisteredClaims
}

type TokenPair struct {
    AccessToken  string `json:"access_token"`
    RefreshToken string `json:"refresh_token"`
    ExpiresIn    int64  `json:"expires_in"`
}

func GenerateToken(config JWTConfig, userID uint, email, role string) (*TokenPair, error) {
    now := time.Now()
    
    accessClaims := Claims{
        UserID: userID,
        Email:  email,
        Role:   role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(now.Add(config.Expire)),
            IssuedAt:  jwt.NewNumericDate(now),
            Issuer:    "myapp",
        },
    }
    
    accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
    accessTokenString, err := accessToken.SignedString([]byte(config.Secret))
    if err != nil {
        return nil, err
    }
    
    refreshClaims := jwt.RegisteredClaims{
        Subject:   string(rune(userID)),
        ExpiresAt: jwt.NewNumericDate(now.Add(config.RefreshExpire)),
        IssuedAt:  jwt.NewNumericDate(now),
    }
    
    refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
    refreshTokenString, err := refreshToken.SignedString([]byte(config.Secret))
    if err != nil {
        return nil, err
    }
    
    return &TokenPair{
        AccessToken:  accessTokenString,
        RefreshToken: refreshTokenString,
        ExpiresIn:    int64(config.Expire.Seconds()),
    }, nil
}

func ParseToken(config JWTConfig, 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("unexpected signing method")
        }
        return []byte(config.Secret), nil
    })
    
    if err != nil {
        return nil, err
    }
    
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    
    return nil, errors.New("invalid token")
}

3.2 认证中间件 #

go
package middleware

import (
    "strings"
    "myapi/pkg/auth"
    "myapi/pkg/response"
    "github.com/labstack/echo/v4"
)

func JWTAuth(config auth.JWTConfig) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            authHeader := c.Request().Header.Get("Authorization")
            if authHeader == "" {
                return response.Unauthorized(c, "请登录")
            }
            
            parts := strings.Split(authHeader, " ")
            if len(parts) != 2 || parts[0] != "Bearer" {
                return response.Unauthorized(c, "Token格式错误")
            }
            
            claims, err := auth.ParseToken(config, parts[1])
            if err != nil {
                return response.Unauthorized(c, "Token无效或已过期")
            }
            
            c.Set("userID", claims.UserID)
            c.Set("email", claims.Email)
            c.Set("role", claims.Role)
            
            return next(c)
        }
    }
}

func RoleAuth(roles ...string) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            role := c.Get("role").(string)
            
            for _, r := range roles {
                if role == r {
                    return next(c)
                }
            }
            
            return response.Forbidden(c, "权限不足")
        }
    }
}

四、认证服务 #

4.1 服务定义 #

go
package services

import (
    "errors"
    "time"
    "myapi/internal/models"
    "myapi/internal/repositories"
    "myapi/pkg/auth"
    "golang.org/x/crypto/bcrypt"
)

type AuthService struct {
    userRepo   *repositories.UserRepository
    jwtConfig  auth.JWTConfig
}

func NewAuthService(userRepo *repositories.UserRepository, jwtConfig auth.JWTConfig) *AuthService {
    return &AuthService{
        userRepo:  userRepo,
        jwtConfig: jwtConfig,
    }
}

type RegisterInput struct {
    Name     string `json:"name" validate:"required"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=6"`
}

type LoginInput struct {
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required"`
}

func (s *AuthService) Register(input *RegisterInput) (*models.User, error) {
    if s.userRepo.ExistsByEmail(input.Email) {
        return nil, errors.New("邮箱已被注册")
    }
    
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
    if err != nil {
        return nil, err
    }
    
    user := &models.User{
        Name:     input.Name,
        Email:    input.Email,
        Password: string(hashedPassword),
        Role:     models.RoleUser,
        Status:   models.StatusActive,
    }
    
    if err := s.userRepo.Create(user); err != nil {
        return nil, err
    }
    
    return user, nil
}

func (s *AuthService) Login(input *LoginInput) (*models.User, *auth.TokenPair, error) {
    user, err := s.userRepo.FindByEmail(input.Email)
    if err != nil {
        return nil, nil, errors.New("邮箱或密码错误")
    }
    
    if user.Status != models.StatusActive {
        return nil, nil, errors.New("账户已被禁用")
    }
    
    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(input.Password)); err != nil {
        return nil, nil, errors.New("邮箱或密码错误")
    }
    
    now := time.Now()
    user.LastLoginAt = &now
    s.userRepo.Update(user)
    
    tokens, err := auth.GenerateToken(s.jwtConfig, user.ID, user.Email, user.Role)
    if err != nil {
        return nil, nil, err
    }
    
    return user, tokens, nil
}

func (s *AuthService) RefreshToken(refreshToken string) (*auth.TokenPair, error) {
    claims, err := auth.ParseToken(s.jwtConfig, refreshToken)
    if err != nil {
        return nil, errors.New("refresh token无效")
    }
    
    user, err := s.userRepo.FindByID(claims.UserID)
    if err != nil {
        return nil, errors.New("用户不存在")
    }
    
    return auth.GenerateToken(s.jwtConfig, user.ID, user.Email, user.Role)
}

func (s *AuthService) GetUserByID(id uint) (*models.User, error) {
    return s.userRepo.FindByID(id)
}

五、认证处理器 #

5.1 处理器定义 #

go
package handlers

import (
    "net/http"
    "myapi/internal/services"
    "myapi/pkg/response"
    "github.com/labstack/echo/v4"
)

type AuthHandler struct {
    authService *services.AuthService
}

func NewAuthHandler(authService *services.AuthService) *AuthHandler {
    return &AuthHandler{authService: authService}
}

func (h *AuthHandler) Register(c echo.Context) error {
    input := new(services.RegisterInput)
    if err := c.Bind(input); err != nil {
        return response.BadRequest(c, "参数格式错误")
    }
    
    if err := c.Validate(input); err != nil {
        return response.BadRequest(c, err.Error())
    }
    
    user, err := h.authService.Register(input)
    if err != nil {
        return response.BadRequest(c, err.Error())
    }
    
    return c.JSON(http.StatusCreated, response.Response{
        Code:    0,
        Message: "注册成功",
        Data:    user,
    })
}

func (h *AuthHandler) Login(c echo.Context) error {
    input := new(services.LoginInput)
    if err := c.Bind(input); err != nil {
        return response.BadRequest(c, "参数格式错误")
    }
    
    if err := c.Validate(input); err != nil {
        return response.BadRequest(c, err.Error())
    }
    
    user, tokens, err := h.authService.Login(input)
    if err != nil {
        return response.Unauthorized(c, err.Error())
    }
    
    return c.JSON(http.StatusOK, response.Response{
        Code:    0,
        Message: "登录成功",
        Data: map[string]interface{}{
            "user":   user,
            "tokens": tokens,
        },
    })
}

func (h *AuthHandler) RefreshToken(c echo.Context) error {
    type RefreshInput struct {
        RefreshToken string `json:"refresh_token" validate:"required"`
    }
    
    input := new(RefreshInput)
    if err := c.Bind(input); err != nil {
        return response.BadRequest(c, "参数格式错误")
    }
    
    tokens, err := h.authService.RefreshToken(input.RefreshToken)
    if err != nil {
        return response.Unauthorized(c, err.Error())
    }
    
    return c.JSON(http.StatusOK, response.Response{
        Code:    0,
        Message: "刷新成功",
        Data:    tokens,
    })
}

func (h *AuthHandler) Profile(c echo.Context) error {
    userID := c.Get("userID").(uint)
    
    user, err := h.authService.GetUserByID(userID)
    if err != nil {
        return response.NotFound(c, "用户不存在")
    }
    
    return response.Success(c, user)
}

func (h *AuthHandler) Logout(c echo.Context) error {
    return response.Success(c, map[string]string{
        "message": "退出成功",
    })
}

六、路由配置 #

6.1 路由设置 #

go
func setupRoutes(e *echo.Echo, authHandler *handlers.AuthHandler, userHandler *handlers.UserHandler, jwtConfig auth.JWTConfig) {
    api := e.Group("/api/v1")
    
    auth := api.Group("/auth")
    {
        auth.POST("/register", authHandler.Register)
        auth.POST("/login", authHandler.Login)
        auth.POST("/refresh", authHandler.RefreshToken)
    }
    
    protected := api.Group("")
    protected.Use(middleware.JWTAuth(jwtConfig))
    {
        protected.GET("/profile", authHandler.Profile)
        protected.POST("/logout", authHandler.Logout)
        
        protected.GET("/users", userHandler.List)
        protected.GET("/users/:id", userHandler.Get)
    }
    
    admin := api.Group("/admin")
    admin.Use(middleware.JWTAuth(jwtConfig))
    admin.Use(middleware.RoleAuth(models.RoleAdmin))
    {
        admin.POST("/users", userHandler.Create)
        admin.PUT("/users/:id", userHandler.Update)
        admin.DELETE("/users/:id", userHandler.Delete)
    }
}

七、完整主程序 #

go
package main

import (
    "fmt"
    "time"
    "myapi/internal/config"
    "myapi/internal/handlers"
    "myapi/internal/models"
    "myapi/internal/repositories"
    "myapi/internal/services"
    "myapi/pkg/auth"
    "myapi/pkg/middleware"
    "myapi/pkg/response"
    "github.com/go-playground/validator/v10"
    "github.com/labstack/echo/v4"
    echomiddleware "github.com/labstack/echo/v4/middleware"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

type CustomValidator struct {
    validator *validator.Validate
}

func (cv *CustomValidator) Validate(i interface{}) error {
    return cv.validator.Struct(i)
}

func main() {
    cfg := config.Load()
    
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        cfg.Database.User,
        cfg.Database.Password,
        cfg.Database.Host,
        cfg.Database.Port,
        cfg.Database.Name,
    )
    
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }
    
    db.AutoMigrate(&models.User{})
    
    e := echo.New()
    
    e.Validator = &CustomValidator{validator: validator.New()}
    
    e.Use(echomiddleware.Recover())
    e.Use(echomiddleware.Logger())
    e.Use(echomiddleware.CORS())
    e.Use(echomiddleware.Gzip())
    
    e.HTTPErrorHandler = func(err error, c echo.Context) {
        if he, ok := err.(*echo.HTTPError); ok {
            response.Error(c, he.Code, fmt.Sprintf("%v", he.Message))
            return
        }
        response.InternalError(c, "服务器内部错误")
    }
    
    jwtConfig := auth.JWTConfig{
        Secret:        cfg.JWT.Secret,
        Expire:        cfg.JWT.Expire,
        RefreshExpire: 7 * 24 * time.Hour,
    }
    
    userRepo := repositories.NewUserRepository(db)
    authService := services.NewAuthService(userRepo, jwtConfig)
    userService := services.NewUserService(userRepo)
    
    authHandler := handlers.NewAuthHandler(authService)
    userHandler := handlers.NewUserHandler(userService)
    
    setupRoutes(e, authHandler, userHandler, jwtConfig)
    
    e.Logger.Fatal(e.Start(":" + cfg.App.Port))
}

八、总结 #

用户认证系统要点:

要点 说明
用户模型 包含角色、状态
JWT工具 Token生成与解析
密码加密 bcrypt加密
认证中间件 JWT验证
角色中间件 权限控制
认证服务 注册、登录、刷新

准备好学习博客系统了吗?让我们进入下一章!

最后更新:2026-03-28