Golang中如何处理几乎所有代码都需要的用户模块

Golang中如何处理几乎所有代码都需要的用户模块 在Go Web应用中,你如何传递已认证的用户结构体?

认证发生在中间件中,而几乎所有处理请求的代码都需要用户信息。

我可以将其存储在Context中,但某种程度上我不喜欢这种方式,因为这种存储不是静态类型的。

或者我几乎给每个方法都添加一个参数。

你是如何处理这个问题的?

3 回复

一种实现方式是在一个早期的处理程序中,将已认证的用户信息存入请求的上下文(Context)中,然后在每个处理程序中再将其取出。

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

更多关于Golang中如何处理几乎所有代码都需要的用户模块的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我通常将其存储在上下文中。但我同意这并不完美,在使用非静态类型存储处理安全相关的内容时必须小心(像零值和拼写错误这类问题可能会带来麻烦)。一般来说,我的大多数API都使用JWT进行身份验证。所以我通常会有一个像 MustBeAuthorized() 这样的中间件,它根据签名密钥验证我的JWT并确保用户已获得授权。通常,这也会在上下文中设置一个UserID(这通常是我在上下文中唯一需要的东西,因为像 IsAdmin 这样的属性会由另一个中间件来验证)。

所以,为了确保处理函数中的拼写错误不会引入bug,我通常会将容易拼错的部分包装在一个全局辅助函数中,然后在各处使用它:

// 为了避免这里的拼写错误...
func MyHandler(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value("UserID").(int)
}

// ... 这样做
func MoreCleverHandler(w http.ResponseWriter, r *http.Request) {
	userID := GetUserID(r)
}

func GetUserID(r *http.Request) int {
	// TODO: 处理错误等
	return r.Context().Value("UserID").(int)
}

然后你可以对你的 GetUserID(或其他)函数进行充分的测试,并相信它能正常工作。这完美吗?不完美——但我稍微倾向于这种方法,而不是给每个函数都添加参数(例如 MyHandler(w http.ResponseWriter, r *http.Request, userID int))。我想如果我走那条路,我会让它更通用/灵活,比如 MyHandler(w http.ResponseWriter, r *http.Request, c TypedContext),这样我就可以向 TypedContext 添加我关心的任何其他字段。

无论如何,我很想听听其他人是怎么做的,以及对他们来说什么方法效果很好。

在Go Web应用中处理用户认证信息传递,我通常采用类型安全的context方式。以下是我的实现方案:

package main

import (
    "context"
    "net/http"
)

// 定义context key类型,避免字符串冲突
type contextKey string

const userContextKey contextKey = "user"

// 用户结构体
type User struct {
    ID       int
    Username string
    Email    string
    Role     string
}

// 中间件:认证并设置用户到context
func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 1. 从请求中获取认证信息(如JWT、Session等)
        token := r.Header.Get("Authorization")
        
        // 2. 验证token并获取用户信息
        user, err := authenticate(token)
        if err != nil {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        
        // 3. 创建新的context并设置用户
        ctx := context.WithValue(r.Context(), userContextKey, user)
        
        // 4. 使用新的context继续处理
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// 类型安全的context获取函数
func UserFromContext(ctx context.Context) (*User, bool) {
    user, ok := ctx.Value(userContextKey).(*User)
    return user, ok
}

// 必须获取用户的版本(失败时panic)
func MustUserFromContext(ctx context.Context) *User {
    user, ok := UserFromContext(ctx)
    if !ok {
        panic("user not found in context")
    }
    return user
}

// 处理器中使用用户信息
func userProfileHandler(w http.ResponseWriter, r *http.Request) {
    // 安全获取用户
    if user, ok := UserFromContext(r.Context()); ok {
        // 使用user处理业务逻辑
        fmt.Fprintf(w, "Welcome %s!", user.Username)
        return
    }
    
    http.Error(w, "Unauthorized", http.StatusUnauthorized)
}

// 需要强制存在用户的处理器
func adminHandler(w http.ResponseWriter, r *http.Request) {
    user := MustUserFromContext(r.Context())
    
    if user.Role != "admin" {
        http.Error(w, "Forbidden", http.StatusForbidden)
        return
    }
    
    // 管理员操作
    fmt.Fprintf(w, "Admin area: %s", user.Username)
}

// 业务逻辑函数示例
func processOrder(ctx context.Context, orderID string) error {
    user, ok := UserFromContext(ctx)
    if !ok {
        return errors.New("user not authenticated")
    }
    
    // 使用user.ID进行数据库操作
    fmt.Printf("Processing order %s for user %d\n", orderID, user.ID)
    return nil
}

// 在HTTP处理器中调用业务函数
func orderHandler(w http.ResponseWriter, r *http.Request) {
    // 传递context给业务层
    err := processOrder(r.Context(), "order123")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.WriteHeader(http.StatusOK)
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/profile", userProfileHandler)
    mux.HandleFunc("/admin", adminHandler)
    mux.HandleFunc("/order", orderHandler)
    
    // 应用认证中间件
    handler := AuthMiddleware(mux)
    
    http.ListenAndServe(":8080", handler)
}

// 模拟认证函数
func authenticate(token string) (*User, error) {
    if token == "" {
        return nil, errors.New("no token provided")
    }
    
    // 实际项目中这里会有JWT解析或数据库查询
    return &User{
        ID:       1,
        Username: "john_doe",
        Email:    "john@example.com",
        Role:     "user",
    }, nil
}

对于需要深度传递的场景,可以结合依赖注入:

// 服务层结构体携带用户上下文
type OrderService struct {
    user *User
}

func NewOrderService(ctx context.Context) (*OrderService, error) {
    user, ok := UserFromContext(ctx)
    if !ok {
        return nil, errors.New("user required")
    }
    
    return &OrderService{user: user}, nil
}

func (s *OrderService) CreateOrder(productID string) error {
    // 直接使用s.user,无需重复传递
    fmt.Printf("Creating order for user %s\n", s.user.Username)
    return nil
}

// 使用示例
func createOrderHandler(w http.ResponseWriter, r *http.Request) {
    service, err := NewOrderService(r.Context())
    if err != nil {
        http.Error(w, err.Error(), http.StatusUnauthorized)
        return
    }
    
    err = service.CreateOrder("prod_123")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.WriteHeader(http.StatusCreated)
}

这种方案保持了类型安全,避免了每个函数都添加用户参数,同时利用Go的context标准机制,与HTTP请求生命周期完美集成。

回到顶部