用户认证系统 #
一、认证系统概述 #
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