使用Golang实现带角色权限的用户认证功能

使用Golang实现带角色权限的用户认证功能 例如,我有5个HTML文件(首页、注册、登录、用户角色保密页面等)。

首先用户需要注册,我有7个输入字段(邮箱、姓名、姓氏、密码、角色等)。 在.go文件中,我从表单获取所有值并存入数据库。

请指导我如何通过会话或Cookie进行用户认证(登录页面),以及它们之间的区别是什么? 我尝试过不使用Cookie,仅通过SQL查询比对用户密码字段与数据库记录(但这种方式可能欠佳,且用户每次都需要重新登录才能查看保密页面)。

我还听说使用会话需要创建另一个数据库。 请告诉我关于注册、登录、用户认证和用户授权的最佳实践方案。

func main() {
    fmt.Println("hello world")
}

更多关于使用Golang实现带角色权限的用户认证功能的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于使用Golang实现带角色权限的用户认证功能的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


以下是基于Golang实现带角色权限的用户认证功能的完整解决方案,包括注册、登录、会话管理和授权控制。

1. 项目结构和依赖

首先安装必要的依赖:

go get github.com/gin-gonic/gin
go get golang.org/x/crypto/bcrypt
go get github.com/gorilla/sessions
go get github.com/mattn/go-sqlite3

2. 数据库模型

package main

import (
    "database/sql"
    "time"
    
    _ "github.com/mattn/go-sqlite3"
    "golang.org/x/crypto/bcrypt"
)

type User struct {
    ID        int       `json:"id"`
    Email     string    `json:"email"`
    FirstName string    `json:"first_name"`
    LastName  string    `json:"last_name"`
    Password  string    `json:"password"`
    Role      string    `json:"role"`
    CreatedAt time.Time `json:"created_at"`
}

type Session struct {
    ID        string    `json:"id"`
    UserID    int       `json:"user_id"`
    ExpiresAt time.Time `json:"expires_at"`
}

func InitDB() (*sql.DB, error) {
    db, err := sql.Open("sqlite3", "./users.db")
    if err != nil {
        return nil, err
    }
    
    // 创建用户表
    userTable := `
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        email TEXT UNIQUE NOT NULL,
        first_name TEXT NOT NULL,
        last_name TEXT NOT NULL,
        password TEXT NOT NULL,
        role TEXT NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )`
    
    // 创建会话表
    sessionTable := `
    CREATE TABLE IF NOT EXISTS sessions (
        id TEXT PRIMARY KEY,
        user_id INTEGER NOT NULL,
        expires_at DATETIME NOT NULL,
        FOREIGN KEY(user_id) REFERENCES users(id)
    )`
    
    _, err = db.Exec(userTable)
    if err != nil {
        return nil, err
    }
    
    _, err = db.Exec(sessionTable)
    return db, err
}

3. 密码加密和验证

func HashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(bytes), err
}

func CheckPasswordHash(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

4. 注册功能

func RegisterHandler(db *sql.DB) gin.HandlerFunc {
    return func(c *gin.Context) {
        var user struct {
            Email     string `form:"email" binding:"required,email"`
            FirstName string `form:"first_name" binding:"required"`
            LastName  string `form:"last_name" binding:"required"`
            Password  string `form:"password" binding:"required,min=6"`
            Role      string `form:"role" binding:"required"`
        }
        
        if err := c.ShouldBind(&user); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        // 检查邮箱是否已存在
        var count int
        err := db.QueryRow("SELECT COUNT(*) FROM users WHERE email = ?", user.Email).Scan(&count)
        if err != nil {
            c.JSON(500, gin.H{"error": "Database error"})
            return
        }
        
        if count > 0 {
            c.JSON(400, gin.H{"error": "Email already exists"})
            return
        }
        
        // 加密密码
        hashedPassword, err := HashPassword(user.Password)
        if err != nil {
            c.JSON(500, gin.H{"error": "Failed to hash password"})
            return
        }
        
        // 插入用户
        result, err := db.Exec(
            "INSERT INTO users (email, first_name, last_name, password, role) VALUES (?, ?, ?, ?, ?)",
            user.Email, user.FirstName, user.LastName, hashedPassword, user.Role,
        )
        
        if err != nil {
            c.JSON(500, gin.H{"error": "Failed to create user"})
            return
        }
        
        userID, _ := result.LastInsertId()
        c.JSON(200, gin.H{"message": "User created successfully", "user_id": userID})
    }
}

5. 会话管理(使用数据库存储会话)

package main

import (
    "crypto/rand"
    "encoding/hex"
    "time"
)

func GenerateSessionID() (string, error) {
    bytes := make([]byte, 32)
    if _, err := rand.Read(bytes); err != nil {
        return "", err
    }
    return hex.EncodeToString(bytes), nil
}

func CreateSession(db *sql.DB, userID int) (string, error) {
    sessionID, err := GenerateSessionID()
    if err != nil {
        return "", err
    }
    
    expiresAt := time.Now().Add(24 * time.Hour) // 24小时过期
    
    _, err = db.Exec(
        "INSERT INTO sessions (id, user_id, expires_at) VALUES (?, ?, ?)",
        sessionID, userID, expiresAt,
    )
    
    if err != nil {
        return "", err
    }
    
    return sessionID, nil
}

func GetUserFromSession(db *sql.DB, sessionID string) (*User, error) {
    // 清理过期会话
    db.Exec("DELETE FROM sessions WHERE expires_at < ?", time.Now())
    
    var user User
    err := db.QueryRow(`
        SELECT u.id, u.email, u.first_name, u.last_name, u.role 
        FROM users u 
        JOIN sessions s ON u.id = s.user_id 
        WHERE s.id = ? AND s.expires_at > ?`,
        sessionID, time.Now(),
    ).Scan(&user.ID, &user.Email, &user.FirstName, &user.LastName, &user.Role)
    
    if err != nil {
        return nil, err
    }
    
    return &user, nil
}

func DeleteSession(db *sql.DB, sessionID string) error {
    _, err := db.Exec("DELETE FROM sessions WHERE id = ?", sessionID)
    return err
}

6. 登录功能

func LoginHandler(db *sql.DB) gin.HandlerFunc {
    return func(c *gin.Context) {
        var credentials struct {
            Email    string `form:"email" binding:"required,email"`
            Password string `form:"password" binding:"required"`
        }
        
        if err := c.ShouldBind(&credentials); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        // 查询用户
        var user User
        err := db.QueryRow(
            "SELECT id, email, first_name, last_name, password, role FROM users WHERE email = ?",
            credentials.Email,
        ).Scan(&user.ID, &user.Email, &user.FirstName, &user.LastName, &user.Password, &user.Role)
        
        if err != nil {
            c.JSON(401, gin.H{"error": "Invalid credentials"})
            return
        }
        
        // 验证密码
        if !CheckPasswordHash(credentials.Password, user.Password) {
            c.JSON(401, gin.H{"error": "Invalid credentials"})
            return
        }
        
        // 创建会话
        sessionID, err := CreateSession(db, user.ID)
        if err != nil {
            c.JSON(500, gin.H{"error": "Failed to create session"})
            return
        }
        
        // 设置Cookie
        c.SetCookie("session_id", sessionID, 86400, "/", "", false, true)
        c.JSON(200, gin.H{
            "message": "Login successful",
            "user": gin.H{
                "id":         user.ID,
                "email":      user.Email,
                "first_name": user.FirstName,
                "last_name":  user.LastName,
                "role":       user.Role,
            },
        })
    }
}

7. 认证中间件

func AuthMiddleware(db *sql.DB) gin.HandlerFunc {
    return func(c *gin.Context) {
        sessionID, err := c.Cookie("session_id")
        if err != nil {
            c.JSON(401, gin.H{"error": "Unauthorized"})
            c.Abort()
            return
        }
        
        user, err := GetUserFromSession(db, sessionID)
        if err != nil {
            c.JSON(401, gin.H{"error": "Invalid session"})
            c.Abort()
            return
        }
        
        // 将用户信息存入上下文
        c.Set("user", user)
        c.Next()
    }
}

8. 基于角色的授权中间件

func RoleMiddleware(allowedRoles ...string) gin.HandlerFunc {
    return func(c *gin.Context) {
        user, exists := c.Get("user")
        if !exists {
            c.JSON(401, gin.H{"error": "Unauthorized"})
            c.Abort()
            return
        }
        
        currentUser := user.(*User)
        
        // 检查用户角色是否在允许的角色列表中
        for _, role := range allowedRoles {
            if currentUser.Role == role {
                c.Next()
                return
            }
        }
        
        c.JSON(403, gin.H{"error": "Insufficient permissions"})
        c.Abort()
    }
}

9. 主程序

package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    // 初始化数据库
    db, err := InitDB()
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    r := gin.Default()
    
    // 静态文件服务
    r.Static("/static", "./static")
    r.LoadHTMLGlob("templates/*")
    
    // 公开路由
    r.GET("/", func(c *gin.Context) {
        c.HTML(200, "index.html", nil)
    })
    
    r.GET("/register", func(c *gin.Context) {
        c.HTML(200, "register.html", nil)
    })
    
    r.GET("/login", func(c *gin.Context) {
        c.HTML(200, "login.html", nil)
    })
    
    // API路由
    r.POST("/api/register", RegisterHandler(db))
    r.POST("/api/login", LoginHandler(db))
    
    // 需要认证的路由
    auth := r.Group("/")
    auth.Use(AuthMiddleware(db))
    {
        auth.GET("/profile", func(c *gin.Context) {
            user, _ := c.Get("user")
            c.HTML(200, "profile.html", gin.H{"user": user})
        })
        
        // 需要管理员权限的路由
        admin := auth.Group("/admin")
        admin.Use(RoleMiddleware("admin"))
        {
            admin.GET("/dashboard", func(c *gin.Context) {
                c.HTML(200, "admin_dashboard.html", nil)
            })
        }
        
        // 需要用户权限的路由
        userRoute := auth.Group("/user")
        userRoute.Use(RoleMiddleware("user", "admin"))
        {
            userRoute.GET("/dashboard", func(c *gin.Context) {
                c.HTML(200, "user_dashboard.html", nil)
            })
        }
    }
    
    // 登出
    r.POST("/api/logout", AuthMiddleware(db), func(c *gin.Context) {
        sessionID, _ := c.Cookie("session_id")
        DeleteSession(db, sessionID)
        c.SetCookie("session_id", "", -1, "/", "", false, true)
        c.JSON(200, gin.H{"message": "Logged out successfully"})
    })
    
    r.Run(":8080")
}

会话和Cookie的区别

Cookie

  • 存储在客户端浏览器中
  • 有大小限制(约4KB)
  • 每次请求都会自动发送到服务器
  • 可以设置过期时间、域名、路径等属性

会话

  • 存储在服务器端(内存、数据库、Redis等)
  • 通过Session ID与客户端关联
  • Session ID通常通过Cookie传输
  • 更安全,敏感数据不存储在客户端

在这个实现中,我们使用数据库存储会话数据,通过Cookie传输Session ID,结合了两者的优点:安全性(敏感数据在服务器端)和便利性(自动传输Session ID)。

回到顶部