用户认证系统实战 #
本节将实现一个完整的用户认证系统,包括用户注册、登录、JWT令牌验证、权限控制等核心功能。
项目结构 #
text
auth-system/
├── main.go
├── config/
│ └── config.go
├── models/
│ └── user.go
├── handlers/
│ └── auth_handler.go
├── middleware/
│ ├── jwt.go
│ └── permission.go
├── services/
│ ├── auth_service.go
│ └── jwt_service.go
├── utils/
│ ├── hash.go
│ └── response.go
└── router/
└── router.go
配置管理 #
go
// config/config.go
package config
import (
"os"
"time"
)
type Config struct {
Server ServerConfig
Database DatabaseConfig
JWT JWTConfig
}
type ServerConfig struct {
Port string
Mode string
}
type DatabaseConfig struct {
Host string
Port string
User string
Password string
DBName string
}
type JWTConfig struct {
Secret string
ExpireTime time.Duration
Issuer string
}
func Load() *Config {
return &Config{
Server: ServerConfig{
Port: getEnv("SERVER_PORT", ":8080"),
Mode: getEnv("GIN_MODE", "debug"),
},
Database: DatabaseConfig{
Host: getEnv("DB_HOST", "localhost"),
Port: getEnv("DB_PORT", "3306"),
User: getEnv("DB_USER", "root"),
Password: getEnv("DB_PASSWORD", ""),
DBName: getEnv("DB_NAME", "auth_db"),
},
JWT: JWTConfig{
Secret: getEnv("JWT_SECRET", "your-secret-key"),
ExpireTime: 24 * time.Hour,
Issuer: "auth-system",
},
}
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
数据模型 #
go
// models/user.go
package models
import (
"time"
"gorm.io/gorm"
)
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
Username string `json:"username" gorm:"uniqueIndex;size:50;not null"`
Email string `json:"email" gorm:"uniqueIndex;size:100;not null"`
Password string `json:"-" gorm:"size:255;not null"`
Nickname string `json:"nickname" gorm:"size:50"`
Avatar string `json:"avatar" gorm:"size:255"`
Role string `json:"role" gorm:"size:20;default:'user'"` // admin, user
Status int `json:"status" gorm:"default:1"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}
func (User) TableName() string {
return "users"
}
type Role string
const (
RoleAdmin Role = "admin"
RoleUser Role = "user"
)
func (r Role) String() string {
return string(r)
}
func (u *User) IsAdmin() bool {
return u.Role == RoleAdmin.String()
}
JWT 服务 #
go
// services/jwt_service.go
package services
import (
"auth-system/config"
"auth-system/models"
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
Role string `json:"role"`
jwt.RegisteredClaims
}
type JWTService struct {
config *config.JWTConfig
}
func NewJWTService(config *config.JWTConfig) *JWTService {
return &JWTService{config: config}
}
func (s *JWTService) GenerateToken(user *models.User) (string, error) {
claims := Claims{
UserID: user.ID,
Username: user.Username,
Role: user.Role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.config.ExpireTime)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: s.config.Issuer,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(s.config.Secret))
}
func (s *JWTService) ParseToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(s.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")
}
func (s *JWTService) RefreshToken(tokenString string) (string, error) {
claims, err := s.ParseToken(tokenString)
if err != nil {
return "", err
}
newClaims := Claims{
UserID: claims.UserID,
Username: claims.Username,
Role: claims.Role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.config.ExpireTime)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: s.config.Issuer,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims)
return token.SignedString([]byte(s.config.Secret))
}
密码工具 #
go
// utils/hash.go
package utils
import (
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
func CheckPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
认证服务 #
go
// services/auth_service.go
package services
import (
"auth-system/models"
"auth-system/utils"
"errors"
"gorm.io/gorm"
)
type AuthService struct {
db *gorm.DB
jwtService *JWTService
}
func NewAuthService(db *gorm.DB, jwtService *JWTService) *AuthService {
return &AuthService{
db: db,
jwtService: jwtService,
}
}
type RegisterRequest struct {
Username string `json:"username" binding:"required,min=3,max=50,alphanum"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=6,max=20"`
Nickname string `json:"nickname" binding:"max=50"`
}
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
type LoginResponse struct {
Token string `json:"token"`
ExpiresAt int64 `json:"expires_at"`
User *models.User `json:"user"`
}
func (s *AuthService) Register(req *RegisterRequest) (*models.User, error) {
var count int64
s.db.Model(&models.User{}).Where("username = ?", req.Username).Count(&count)
if count > 0 {
return nil, errors.New("用户名已存在")
}
s.db.Model(&models.User{}).Where("email = ?", req.Email).Count(&count)
if count > 0 {
return nil, errors.New("邮箱已被注册")
}
hashedPassword, err := utils.HashPassword(req.Password)
if err != nil {
return nil, errors.New("密码加密失败")
}
user := &models.User{
Username: req.Username,
Email: req.Email,
Password: hashedPassword,
Nickname: req.Nickname,
Role: models.RoleUser.String(),
Status: 1,
}
if err := s.db.Create(user).Error; err != nil {
return nil, err
}
return user, nil
}
func (s *AuthService) Login(req *LoginRequest) (*LoginResponse, error) {
var user models.User
if err := s.db.Where("username = ? OR email = ?", req.Username, req.Username).First(&user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("用户不存在")
}
return nil, err
}
if user.Status != 1 {
return nil, errors.New("用户已被禁用")
}
if !utils.CheckPassword(req.Password, user.Password) {
return nil, errors.New("密码错误")
}
token, err := s.jwtService.GenerateToken(&user)
if err != nil {
return nil, errors.New("生成令牌失败")
}
return &LoginResponse{
Token: token,
ExpiresAt: 0,
User: &user,
}, nil
}
func (s *AuthService) GetUserByID(id uint) (*models.User, error) {
var user models.User
if err := s.db.First(&user, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("用户不存在")
}
return nil, err
}
return &user, nil
}
func (s *AuthService) ChangePassword(userID uint, oldPassword, newPassword string) error {
user, err := s.GetUserByID(userID)
if err != nil {
return err
}
if !utils.CheckPassword(oldPassword, user.Password) {
return errors.New("原密码错误")
}
hashedPassword, err := utils.HashPassword(newPassword)
if err != nil {
return errors.New("密码加密失败")
}
return s.db.Model(user).Update("password", hashedPassword).Error
}
JWT 中间件 #
go
// middleware/jwt.go
package middleware
import (
"auth-system/services"
"auth-system/utils"
"strings"
"time"
"github.com/gin-gonic/gin"
)
func JWTAuth(jwtService *services.JWTService) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
utils.Unauthorized(c, "请先登录")
c.Abort()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
utils.Unauthorized(c, "认证格式错误")
c.Abort()
return
}
claims, err := jwtService.ParseToken(parts[1])
if err != nil {
utils.Unauthorized(c, "令牌无效或已过期")
c.Abort()
return
}
c.Set("user_id", claims.UserID)
c.Set("username", claims.Username)
c.Set("role", claims.Role)
c.Set("claims", claims)
c.Next()
}
}
func GetUserID(c *gin.Context) uint {
if userID, exists := c.Get("user_id"); exists {
return userID.(uint)
}
return 0
}
func GetUsername(c *gin.Context) string {
if username, exists := c.Get("username"); exists {
return username.(string)
}
return ""
}
func GetRole(c *gin.Context) string {
if role, exists := c.Get("role"); exists {
return role.(string)
}
return ""
}
func GetClaims(c *gin.Context) *services.Claims {
if claims, exists := c.Get("claims"); exists {
return claims.(*services.Claims)
}
return nil
}
权限中间件 #
go
// middleware/permission.go
package middleware
import (
"auth-system/utils"
"github.com/gin-gonic/gin"
)
func RequireRole(roles ...string) gin.HandlerFunc {
return func(c *gin.Context) {
userRole := GetRole(c)
for _, role := range roles {
if userRole == role {
c.Next()
return
}
}
utils.Forbidden(c, "权限不足")
c.Abort()
}
}
func RequireAdmin() gin.HandlerFunc {
return RequireRole("admin")
}
func OptionalAuth(jwtService *services.JWTService) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.Next()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
c.Next()
return
}
claims, err := jwtService.ParseToken(parts[1])
if err != nil {
c.Next()
return
}
c.Set("user_id", claims.UserID)
c.Set("username", claims.Username)
c.Set("role", claims.Role)
c.Set("claims", claims)
c.Next()
}
}
响应工具 #
go
// utils/response.go
package utils
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
func Success(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, Response{
Code: 0,
Message: "success",
Data: data,
})
}
func Created(c *gin.Context, data interface{}) {
c.JSON(http.StatusCreated, Response{
Code: 0,
Message: "created successfully",
Data: data,
})
}
func BadRequest(c *gin.Context, message string) {
c.JSON(http.StatusBadRequest, Response{
Code: http.StatusBadRequest,
Message: message,
})
}
func Unauthorized(c *gin.Context, message string) {
c.JSON(http.StatusUnauthorized, Response{
Code: http.StatusUnauthorized,
Message: message,
})
}
func Forbidden(c *gin.Context, message string) {
c.JSON(http.StatusForbidden, Response{
Code: http.StatusForbidden,
Message: message,
})
}
func NotFound(c *gin.Context, message string) {
c.JSON(http.StatusNotFound, Response{
Code: http.StatusNotFound,
Message: message,
})
}
func ValidationError(c *gin.Context, errors interface{}) {
c.JSON(http.StatusUnprocessableEntity, Response{
Code: http.StatusUnprocessableEntity,
Message: "validation failed",
Data: errors,
})
}
func InternalError(c *gin.Context, message string) {
c.JSON(http.StatusInternalServerError, Response{
Code: http.StatusInternalServerError,
Message: message,
})
}
认证处理器 #
go
// handlers/auth_handler.go
package handlers
import (
"auth-system/middleware"
"auth-system/services"
"auth-system/utils"
"github.com/gin-gonic/gin"
)
type AuthHandler struct {
authService *services.AuthService
jwtService *services.JWTService
}
func NewAuthHandler(authService *services.AuthService, jwtService *services.JWTService) *AuthHandler {
return &AuthHandler{
authService: authService,
jwtService: jwtService,
}
}
func (h *AuthHandler) Register(c *gin.Context) {
var req services.RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, "请求参数错误")
return
}
user, err := h.authService.Register(&req)
if err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.Created(c, user)
}
func (h *AuthHandler) Login(c *gin.Context) {
var req services.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, "请求参数错误")
return
}
resp, err := h.authService.Login(&req)
if err != nil {
utils.Unauthorized(c, err.Error())
return
}
utils.Success(c, resp)
}
func (h *AuthHandler) GetProfile(c *gin.Context) {
userID := middleware.GetUserID(c)
user, err := h.authService.GetUserByID(userID)
if err != nil {
utils.NotFound(c, err.Error())
return
}
utils.Success(c, user)
}
func (h *AuthHandler) ChangePassword(c *gin.Context) {
var req struct {
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=6,max=20"`
}
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, "请求参数错误")
return
}
userID := middleware.GetUserID(c)
if err := h.authService.ChangePassword(userID, req.OldPassword, req.NewPassword); err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.Success(c, gin.H{"message": "密码修改成功"})
}
func (h *AuthHandler) RefreshToken(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
utils.Unauthorized(c, "请先登录")
return
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 {
utils.Unauthorized(c, "认证格式错误")
return
}
newToken, err := h.jwtService.RefreshToken(parts[1])
if err != nil {
utils.Unauthorized(c, "令牌刷新失败")
return
}
utils.Success(c, gin.H{"token": newToken})
}
路由配置 #
go
// router/router.go
package router
import (
"auth-system/config"
"auth-system/handlers"
"auth-system/middleware"
"auth-system/models"
"auth-system/services"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
func Setup(db *gorm.DB, cfg *config.Config) *gin.Engine {
gin.SetMode(cfg.Server.Mode)
r := gin.Default()
jwtService := services.NewJWTService(&cfg.JWT)
authService := services.NewAuthService(db, jwtService)
authHandler := handlers.NewAuthHandler(authService, jwtService)
db.AutoMigrate(&models.User{})
api := r.Group("/api/v1")
{
auth := api.Group("/auth")
{
auth.POST("/register", authHandler.Register)
auth.POST("/login", authHandler.Login)
auth.POST("/refresh", authHandler.RefreshToken)
}
user := api.Group("/user")
user.Use(middleware.JWTAuth(jwtService))
{
user.GET("/profile", authHandler.GetProfile)
user.PUT("/password", authHandler.ChangePassword)
}
admin := api.Group("/admin")
admin.Use(middleware.JWTAuth(jwtService))
admin.Use(middleware.RequireAdmin())
{
admin.GET("/users", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "admin panel"})
})
}
}
return r
}
主程序 #
go
// main.go
package main
import (
"auth-system/config"
"auth-system/router"
"fmt"
"log"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
cfg := config.Load()
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
cfg.Database.User,
cfg.Database.Password,
cfg.Database.Host,
cfg.Database.Port,
cfg.Database.DBName,
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("数据库连接失败:", err)
}
r := router.Setup(db, cfg)
log.Println("服务器启动在", cfg.Server.Port)
if err := r.Run(cfg.Server.Port); err != nil {
log.Fatal("服务器启动失败:", err)
}
}
API 使用示例 #
用户注册 #
bash
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"username": "johndoe",
"email": "john@example.com",
"password": "password123",
"nickname": "John"
}'
用户登录 #
bash
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "johndoe",
"password": "password123"
}'
响应:
json
{
"code": 0,
"message": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": 1711622400,
"user": {
"id": 1,
"username": "johndoe",
"email": "john@example.com",
"nickname": "John",
"role": "user"
}
}
}
获取用户信息 #
bash
curl http://localhost:8080/api/v1/user/profile \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
修改密码 #
bash
curl -X PUT http://localhost:8080/api/v1/user/password \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"old_password": "password123",
"new_password": "newpassword456"
}'
刷新令牌 #
bash
curl -X POST http://localhost:8080/api/v1/auth/refresh \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
安全最佳实践 #
1. 密码安全 #
go
import "golang.org/x/crypto/bcrypt"
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
func CheckPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
2. JWT 安全配置 #
go
type JWTConfig struct {
Secret string
ExpireTime time.Duration
Issuer string
}
func Load() *Config {
return &Config{
JWT: JWTConfig{
Secret: os.Getenv("JWT_SECRET"),
ExpireTime: 24 * time.Hour,
Issuer: "auth-system",
},
}
}
3. 限流保护 #
go
import "github.com/ulule/limiter/v3"
func RateLimit() gin.HandlerFunc {
store := memory.NewStore()
rate := limiter.Rate{
Period: 1 * time.Minute,
Limit: 60,
}
instance := limiter.New(store, rate)
return func(c *gin.Context) {
context, err := instance.Get(c, c.ClientIP())
if err != nil {
c.AbortWithStatus(500)
return
}
if context.Reached {
c.AbortWithStatusJSON(429, gin.H{
"error": "too many requests",
})
return
}
c.Next()
}
}
4. CORS 配置 #
go
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
小结 #
本节实现了一个完整的用户认证系统:
- 用户注册登录:支持用户名/邮箱登录
- JWT令牌:生成、验证、刷新
- 密码加密:使用bcrypt
- 权限控制:基于角色的访问控制
- 安全实践:限流、CORS等
这个认证系统可以直接用于生产环境,也可以根据需求扩展更多功能。
最后更新:2026-03-28