Go: برمجيات الوسيطة Go ومصادقة JWT

آخر تحديث: 2026-08-26

البرمجيات الوسيطة هي «مسار» الخدمات الصغيرة في لغة Go — المصادقة، وتحديد معدل الاستخدام، والتسجيل، والاستعادة؛ فكل مسألة شاملة تعد جزءًا من البرمجيات الوسيطة.

عندما تحتاج إلى التحقق من مصادقة JWT، أو تسجيل الأنشطة، أو فرض حدود المعدل في كل نقطة نهاية لواجهة برمجة التطبيقات (API)، فإن نمط «البرمجيات الوسيطة» يتيح لك كتابة الكود مرة واحدة وتطبيقه في كل مكان.

1. ستتعلم



2. قصة حقيقية لمهندس برمجيات الخلفية

(1) المشكلة: التحقق يدويًّا من الرمز المميز في كل جهاز توجيه؛ فقد نسيت القيام بذلك ثلاث مرات.

يعمل تشارلي ضمن فريق API Gateway، وهو مسؤول عن المصادقة لجميع نقاط النهاية:

"طلب المدير أن تقوم جميع واجهات برمجة التطبيقات (API) بالتحقق من صحة رموز JWT. فقمت بنسخ ولصق 15 سطراً من كود التحقق من صحة الرموز في كل معالج. وفي اليوم الثاني بعد النشر، نسيت إضافة ميزة المصادقة إلى /admin/users — مما سمح لأي شخص بحذف المستخدمين مباشرةً. فقال المدير التقني: «كشفت عملية التدقيق الأمني عن ثغرة أمنية خطيرة.»"

GO
// Bad code: manually check in every handler
func deleteUser(w http.ResponseWriter, r *http.Request) {
    // Forgot to add Token verification! Serious security vulnerability
    userID := r.PathValue("id")
    deleteUserFromDB(userID)
}

func updateOrder(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("Authorization")
    // Copy 15 lines of verification code each time
    if !validateToken(token) {
        http.Error(w, "unauthorized", 401)
        return
    }
    // Business logic...
}

(2) حل Go: برمجيات وسيطة للمصادقة

GO
// Good code: auth middleware, implement once, apply everywhere
func AuthMiddleware(jwtSecret string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            token := extractToken(r)
            claims, err := validateJWT(token, jwtSecret)
            if err != nil {
                writeError(w, http.StatusUnauthorized, "invalid token")
                return
            }
            // Inject user info into Context
            ctx := context.WithValue(r.Context(), "user", claims)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// Registration only needs one Middleware
mux.Handle("POST /api/orders", AuthMiddleware(secret)(orderHandler))

(3) المزايا: الطريقة اليدوية مقابل البرمجيات الوسيطة

البعد الفحص اليدوي نمط البرمجيات الوسيطة
خطر الإغفال هناك خطر نسيان إضافة كل مسار جديد صفر
تكرار الكود 15 سطراً لكل معالج 0 سطر (تركز المعالجات حصريًّا على المنطق التجاري)
تعديل المنطق تعديل 20 معالجًا تعديل برنامج وسيط واحد
اختبار الوحدة اختبار المصادقة لكل معالج اختبار البرمجيات الوسيطة مرة واحدة فقط


3. المصادقة باستخدام JWT

▶ مثال: إنشاء JWT والتحقق من صحته

⚙️ المتطلبات الأساسية: في بيئة الإنتاج، استخدم go get github.com/golang-jwt/jwt/v5. الشفرة أدناه هي تطبيق يدوي لأغراض تعليمية فقط.

GO 📖 للعرض فقط
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "strings"
    "time"
)

// ---------- JWT manual impl (no third-party dependency) ----------

type JWTClaims struct {
    UserID      int      `json:"user_id"`
    Role        string   `json:"role"`
    Permissions []string `json:"permissions"`
    ExpiresAt   int64    `json:"exp"`
}

type JWTHeader struct {
    Alg string `json:"alg"`
    Typ string `json:"typ"`
}

func base64Encode(data []byte) string {
    return strings.TrimRight(base64.URLEncoding.EncodeToString(data), "=")
}

func base64Decode(s string) ([]byte, error) {
    // Pad to proper length
    switch len(s) % 4 {
    case 2:
        s += "=="
    case 3:
        s += "="
    }
    return base64.URLEncoding.DecodeString(s)
}

func createJWT(claims JWTClaims, secret string) (string, error) {
    header := JWTHeader{Alg: "HS256", Typ: "JWT"}
    headerJSON, _ := json.Marshal(header)
    claimsJSON, _ := json.Marshal(claims)

    headerEnc := base64Encode(headerJSON)
    claimsEnc := base64Encode(claimsJSON)

    // Sign
    message := headerEnc + "." + claimsEnc
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    signature := base64Encode(mac.Sum(nil))

    return message + "." + signature, nil
}

func validateJWT(token string, secret string) (*JWTClaims, error) {
    parts := strings.Split(token, ".")
    if len(parts) != 3 {
        return nil, fmt.Errorf("invalid token format")
    }

    // Verify signature
    message := parts[0] + "." + parts[1]
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    expectedSig := base64Encode(mac.Sum(nil))

    if !hmac.Equal([]byte(parts[2]), []byte(expectedSig)) {
        return nil, fmt.Errorf("invalid signature")
    }

    // Parse claims
    claimsJSON, err := base64Decode(parts[1])
    if err != nil {
        return nil, err
    }

    var claims JWTClaims
    if err := json.Unmarshal(claimsJSON, &claims); err != nil {
        return nil, err
    }

    // Check expiration
    if time.Now().Unix() > claims.ExpiresAt {
        return nil, fmt.Errorf("token expired")
    }

    return &claims, nil
}

func main() {
    secret := "my-secret-key"

    // Generate Token
    claims := JWTClaims{
        UserID:   1,
        Role:     "admin",
        ExpiresAt: time.Now().Add(1 * time.Hour).Unix(),
    }

    token, _ := createJWT(claims, secret)
    fmt.Printf("JWT: %s\n", token)

    // Validate Token
    validated, err := validateJWT(token, secret)
    if err != nil {
        fmt.Printf("Validation failed: %v\n", err)
    } else {
        fmt.Printf("Validation succeeded: user=%d, role=%s\n", validated.UserID, validated.Role)
    }
}
85 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)
💡 نصيحة: في بيئة الإنتاج، يرجى استخدام مكتبة JWT مجربة وموثوقة (github.com/golang-jwt/jwt/v5) بدلاً من تنفيذها يدويًّا. يهدف الكود أعلاه حصريًّا إلى توضيح كيفية عمل JWT. تتعامل مكتبات الجهات الخارجية مع المزيد من الحالات الاستثنائية (مثل أنواع الرموز المميزة، وتناوب المفاتيح، والتحقق من المطالبات القياسية).



4. رمز الوصول + رمز التحديث

▶ مثال: المصادقة باستخدام رمزين

GO 📖 للعرض فقط
package main

import (
    "crypto/rand"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "net/http"
    "sync"
    "time"
)

// ---------- Token Service ----------

type TokenService struct {
    secret     string
    refreshTTL time.Duration
    // Store refresh tokens (use Redis in production)
    refreshTokens map[string]int // token → userID
    mu            sync.RWMutex
}

func NewTokenService(secret string) *TokenService {
    return &TokenService{
        secret:         secret,
        refreshTTL:     7 * 24 * time.Hour,
        refreshTokens:  make(map[string]int),
    }
}

func (s *TokenService) GenerateTokens(userID int, role string) (accessToken, refreshToken string, err error) {
    // Access Token (expires in 15 minutes)
    accessClaims := JWTClaims{
        UserID:    userID,
        Role:      role,
        ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
    }
    accessToken, err = createJWT(accessClaims, s.secret)
    if err != nil {
        return "", "", err
    }

    // Refresh Token (random string, expires in 7 days)
    bytes := make([]byte, 32)
    rand.Read(bytes)
    refreshToken = hex.EncodeToString(bytes)

    s.mu.Lock()
    s.refreshTokens[refreshToken] = userID
    s.mu.Unlock()

    return accessToken, refreshToken, nil
}

func (s *TokenService) RefreshAccessToken(refreshToken string) (string, error) {
    s.mu.RLock()
    userID, exists := s.refreshTokens[refreshToken]
    s.mu.RUnlock()

    if !exists {
        return "", fmt.Errorf("invalid refresh token")
    }

    // Generate new Access Token
    claims := JWTClaims{
        UserID:    userID,
        Role:      "user",
        ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
    }
    return createJWT(claims, s.secret)
}

func (s *TokenService) RevokeRefreshToken(refreshToken string) {
    s.mu.Lock()
    delete(s.refreshTokens, refreshToken)
    s.mu.Unlock()
}

// ---------- Auth Handler ----------

type AuthHandler struct {
    tokenSvc *TokenService
}

type loginRequest struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

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

func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
    var req loginRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
        return
    }

    // Verify username and password (use bcrypt in production)
    if req.Username != "admin" || req.Password != "password" {
        writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid credentials"})
        return
    }

    accessToken, refreshToken, err := h.tokenSvc.GenerateTokens(1, "admin")
    if err != nil {
        writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "token generation failed"})
        return
    }

    writeJSON(w, http.StatusOK, tokenResponse{
        AccessToken:  accessToken,
        RefreshToken: refreshToken,
        TokenType:    "Bearer",
        ExpiresIn:    900, // 15 minutes
    })
}

func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
    var req struct {
        RefreshToken string `json:"refresh_token"`
    }
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
        return
    }

    accessToken, err := h.tokenSvc.RefreshAccessToken(req.RefreshToken)
    if err != nil {
        writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid refresh token"})
        return
    }

    writeJSON(w, http.StatusOK, map[string]string{"access_token": accessToken})
}

func main() {
    tokenSvc := NewTokenService("my-secret-key")
    authHandler := &AuthHandler{tokenSvc: tokenSvc}

    mux := http.NewServeMux()
    mux.HandleFunc("POST /login", authHandler.Login)
    mux.HandleFunc("POST /refresh", authHandler.Refresh)

    fmt.Println("Auth service listening on :8080")
    http.ListenAndServe(":8080", mux)
}

func writeJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}
124 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)
100%
sequenceDiagram
    participant Client
    participant API as API Gateway
    participant Auth as Auth Service

    Client->>API: POST /login (username, password)
    API->>Auth: Verify credentials
    Auth-->>API: access_token (15min) + refresh_token (7d)
    API-->>Client: Return dual tokens

    Client->>API: GET /orders (Bearer access_token)
    API->>API: Validate access_token
    API-->>Client: 200 OK

    Client->>API: GET /orders (access_token expired)
    API-->>Client: 401 Unauthorized

    Client->>API: POST /refresh (refresh_token)
    API->>Auth: Validate refresh_token
    Auth-->>API: New access_token
    API-->>Client: Return new access_token


5. البرمجيات الوسيطة الخاصة بأذونات RBAC

▶ مثال: تطبيق نموذج RBAC

GO 📖 للعرض فقط
package main

import (
    "context"
    "encoding/json"
    "net/http"
)

type UserRole string

const (
    RoleAdmin UserRole = "admin"
    RoleUser  UserRole = "user"
    RoleGuest UserRole = "guest"
)

// Permission definitions
var rolePermissions = map[UserRole][]string{
    RoleAdmin: {"read:users", "write:users", "حذف:users", "read:orders", "write:orders"},
    RoleUser:  {"read:orders", "write:orders"},
    RoleGuest: {"read:products"},
}

type Claims struct {
    UserID int
    Role   UserRole
}

// Get claims from Context
func GetClaims(r *http.Request) *Claims {
    claims, _ := r.Context().Value("claims").(*Claims)
    return claims
}

// RBAC middleware
func RequirePermission(permission string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            claims := GetClaims(r)
            if claims == nil {
                writeError(w, http.StatusUnauthorized, "not authenticated")
                return
            }

            permissions, exists := rolePermissions[claims.Role]
            if !exists {
                writeError(w, http.StatusForbidden, "no permissions defined for role")
                return
            }

            // Check permission
            hasPermission := false
            for _, p := range permissions {
                if p == permission {
                    hasPermission = true
                    break
                }
            }

            if !hasPermission {
                writeError(w, http.StatusForbidden, "insufficient permissions")
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}

// ---------- Route registration ----------

func setupRoutes() http.Handler {
    mux := http.NewServeMux()

    // Public endpoints
    mux.HandleFunc("POST /login", loginHandler)

    // Authenticated endpoints
    mux.Handle("GET /api/orders", RequirePermission("read:orders")(authMiddleware(http.HandlerFunc(listOrders))))
    mux.Handle("POST /api/orders", RequirePermission("write:orders")(authMiddleware(http.HandlerFunc(createOrder))))
    mux.Handle("DELETE /api/users/{id}", RequirePermission("حذف:users")(authMiddleware(http.HandlerFunc(deleteUser))))

    return mux
}
61 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

(2) مسار البرمجيات الوسيطة

TEXT 📖 للعرض فقط
Request → Auth Middleware → RBAC Middleware → Handler
              |                   |
         Verify JWT Token    Check Role permissions
         Inject Claims       User has permission?
المستوى البرمجيات الوسيطة المسؤوليات
1 الاسترداد Catch panic، return 500
2 التسجيل مدة طلب السجل
3 CORS معالجة الطلبات عبر المصادر
4 المصادقة التحقق من JWT، وإدراج معلومات المستخدم
5 RBAC التحقق من أذونات الدور
6 الحد الأقصى للمعدل التحكم في معدل الاستخدام باستخدام «Token Bucket»
7 المعالج منطق الأعمال


6. التحكم في التدفق باستخدام نموذج «دلو الرموز»

▶ مثال: تطبيق نموذج «دلو الرموز»

GO 📖 للعرض فقط
package main

import (
    "net/http"
    "sync"
    "time"
)

type TokenBucket struct {
    mu         sync.Mutex
    tokens     float64
    maxTokens  float64
    refillRate float64
    lastRefill time.Time
}

func NewTokenBucket(rate float64, burst int) *TokenBucket {
    return &TokenBucket{
        tokens:     float64(burst),
        maxTokens:  float64(burst),
        refillRate: rate,
        lastRefill: time.Now(),
    }
}

func (tb *TokenBucket) Allow() bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()

    // Refill tokens
    now := time.Now()
    elapsed := now.Sub(tb.lastRefill).Seconds()
    tb.tokens = min(tb.tokens+elapsed*tb.refillRate, tb.maxTokens)
    tb.lastRefill = now

    // Take a token
    if tb.tokens >= 1 {
        tb.tokens--
        return true
    }
    return false
}

func min(a, b float64) float64 {
    if a < b {
        return a
    }
    return b
}

// IP rate limiter
type IPRateLimiter struct {
    mu       sync.RWMutex
    buckets  map[string]*TokenBucket
    rate     float64
    burst    int
}

func NewIPRateLimiter(rate float64, burst int) *IPRateLimiter {
    return &IPRateLimiter{
        buckets: make(map[string]*TokenBucket),
        rate:    rate,
        burst:   burst,
    }
}

func (rl *IPRateLimiter) GetBucket(ip string) *TokenBucket {
    rl.mu.Lock()
    defer rl.mu.Unlock()

    bucket, exists := rl.buckets[ip]
    if !exists {
        bucket = NewTokenBucket(rl.rate, rl.burst)
        rl.buckets[ip] = bucket
    }
    return bucket
}

// RateLimiterMiddleware
func RateLimiterMiddleware(rate float64, burst int) Middleware {
    limiter := NewIPRateLimiter(rate, burst)

    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ip := r.RemoteAddr
            bucket := limiter.GetBucket(ip)

            if !bucket.Allow() {
                w.Header().Set("Retry-After", "1")
                writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}
78 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

7. مثال كامل: مسار عمل البرمجيات الوسيطة في API Gateway

▶ مثال: مسار العمل الكامل

GO 📖 للعرض فقط
// api_gateway.go
package main

import (
    "context"
    "encoding/json"
    "log"
    "net/http"
    "os"
    "os/signal"
    "strings"
    "syscall"
    "time"
)

// ---------- Type definitions ----------

type Middleware func(http.Handler) http.Handler

type Claims struct {
    UserID int
    Role   string
}

// ---------- Middleware ----------

// Recovery
func Recovery(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("[PANIC] %v", err)
                writeError(w, http.StatusInternalServerError, "internal error")
            }
        }()
        next.ServeHTTP(w, r)
    })
}

// Logging
func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
        next.ServeHTTP(w, r)
        log.Printf("[%s] %s → %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// CORS
func CORS(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// Auth (JWT authentication)
func Auth(secret string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authHeader := r.Header.Get("Authorization")
            if !strings.HasPrefix(authHeader, "Bearer ") {
                writeError(w, http.StatusUnauthorized, "missing token")
                return
            }

            token := authHeader[7:]
            claims, err := validateJWT(token, secret)
            if err != nil {
                writeError(w, http.StatusUnauthorized, err.Error())
                return
            }

            ctx := context.WithValue(r.Context(), "claims", &Claims{
                UserID: claims.UserID,
                Role:   claims.Role,
            })
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// RBAC
func RequireRole(roles ...string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            claims, ok := r.Context().Value("claims").(*Claims)
            if !ok {
                writeError(w, http.StatusUnauthorized, "not authenticated")
                return
            }

            for _, role := range roles {
                if claims.Role == role {
                    next.ServeHTTP(w, r)
                    return
                }
            }

            writeError(w, http.StatusForbidden, "insufficient permissions")
        })
    }
}

// Rate Limiter
var rateLimiter = NewIPRateLimiter(10, 20) // 10 req/s, burst 20

func RateLimit(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ip := r.RemoteAddr
        bucket := rateLimiter.GetBucket(ip)
        if !bucket.Allow() {
            writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
            return
        }
        next.ServeHTTP(w, r)
    })
}

// ---------- Chain ----------

func Chain(h http.Handler, mws ...Middleware) http.Handler {
    for i := len(mws) - 1; i >= 0; i-- {
        h = mws[i](h)
    }
    return h
}

// ---------- Handlers ----------

type OrderHandler struct{}

func (h *OrderHandler) ListOrders(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[string]string{"orders": "list"})
}

func (h *OrderHandler) CreateOrder(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusCreated, map[string]string{"الترتيب": "created"})
}

type AdminHandler struct{}

func (h *AdminHandler) DeleteUser(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[string]string{"deleted": "user"})
}

// ---------- Utility functions ----------

func writeJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

func writeError(w http.ResponseWriter, status int, msg string) {
    writeJSON(w, status, map[string]string{"error": msg})
}

// ---------- Main ----------

func main() {
    jwtSecret := "super-secret-key"
    orders := &OrderHandler{}

    mux := http.NewServeMux()

    // Public endpoint
    mux.HandleFunc("POST /login", func(w http.ResponseWriter, r *http.Request) {
        writeJSON(w, http.StatusOK, map[string]string{"token": "login-token"})
    })

    // Authenticated + rate-limited endpoints
    mux.Handle("GET /api/orders", Chain(
        http.HandlerFunc(orders.ListOrders),
        Auth(jwtSecret),
        RateLimit,
    ))

    mux.Handle("POST /api/orders", Chain(
        http.HandlerFunc(orders.CreateOrder),
        Auth(jwtSecret),
        RateLimit,
    ))

    // Admin endpoints (Auth + RBAC + RateLimit)
    mux.Handle("DELETE /api/users/{id}", Chain(
        http.HandlerFunc((&AdminHandler{}).DeleteUser),
        Auth(jwtSecret),
        RequireRole("admin"),
        RateLimit,
    ))

    // Global middleware (Recovery → Logging → CORS → routes)
    app := Chain(mux, Recovery, Logging, CORS)

    server := &http.Server{Addr: ":8080", Handler: app}

    go func() {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
        <-sigCh
        log.Println("Shutting down...")
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        server.Shutdown(ctx)
    }()

    log.Println("API Gateway listening on :8080")
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatal(err)
    }
}
164 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)
🔥 خطأ شائع: ترتيب برامج الوسيطة أمر بالغ الأهمية. يجب أن تكون «الاستعادة» هي الطبقة الخارجية (للتعامل مع جميع حالات الذعر). يجب أن يأتي كل من المصادقة (Auth) ونظام التحكم في الوصول القائم على الأدوار (RBAC) قبل تحديد معدل الاستخدام (RateLimit) — حيث يجب أن ينطبق تحديد معدل الاستخدام على الطلبات غير المصادق عليها أيضًا، ولكن لا ينبغي السماح للمستخدمين غير المصادق عليهم باستهلاك الرموز (tokens) أولاً. الترتيب العام: الاسترداد (Recovery) → التسجيل (Logging) → CORS → المصادقة (Auth) → نظام التحكم في الوصول القائم على الأدوار (RBAC) → تحديد معدل الاستخدام (RateLimit) → المعالج (Handler).



❓ أسئلة شائعة

س كيف يتم تنفيذ JWT؟
ج يتكون JWT من ثلاثة أجزاء: الرأس (الخوارزمية + النوع)، والحمولة (المطالبات)، والتوقيع. توصي لغة Go باستخدام github.com/golang-jwt/jwt/v5. تتميز رموز الوصول بفترة صلاحية قصيرة (15 دقيقة)، في حين تتميز رموز التحديث بفترة صلاحية طويلة (7 أيام) ويتم تخزينها على الخادم.
س ما الفرق بين رمز الوصول (Access Token) ورمز التحديث (Refresh Token)؟
ج يُستخدم رمز الوصول للمصادقة على واجهة برمجة التطبيقات (API)؛ وهو صالح لفترة قصيرة (15 دقيقة) ويحتوي على معلومات المستخدم وأذوناته. أما رمز التحديث فيُستخدم للحصول على رمز وصول جديد؛ وهو صالح لفترة طويلة (7 أيام) ويتم تخزينه على الخادم. وعندما تنتهي صلاحية رمز الوصول، يستخدم العميل رمز التحديث للحصول تلقائيًا على رمز وصول جديد دون الحاجة إلى قيام المستخدم بتسجيل الدخول مرة أخرى.
س كيف يتم تنفيذ نظام RBAC؟
ج يقوم نظام RBAC (التحكم في الوصول القائم على الأدوار) بتعيين مجموعة من الأذونات لكل دور، وتقوم البرمجيات الوسيطة بالتحقق مما إذا كان دور المستخدم الحالي يمتلك الأذونات المطلوبة للطلب. التنفيذ: rolePermissions[role] = []permission + تقوم البرمجيات الوسيطة بالتحقق permission in rolePermissions[claims.Role].
س ما هي خوارزمية تحديد معدل الاستخدام التي ينبغي أن أختارها؟
ج تُعد خوارزمية «دلو الرموز» (token bucket) هي الأكثر استخدامًا — فهي تسمح بحدوث طفرات في معدل الاستخدام وتتيح التحكم في المعدل المتوسط. طريقة التنفيذ: يُعاد ملء الدلو برموز المعدل في الثانية، مع تحديد حد أقصى للطفرات. يستهلك كل طلب رمزًا واحدًا؛ وإذا لم تكن الرموز كافية، يتم إرجاع خطأ 429. وهي مناسبة لتحديد معدل استخدام واجهة برمجة التطبيقات (API).
س كيف يمكنني منع برنامج الوسيط الخاص بالاستعادة من إخفاء رسائل الذعر؟
ج قم بتسجيل تتبع المكدس الكامل في عملية الاستعادة، ثم أرجعه باستخدام نوع خطأ مخصص. تقوم debug.Stack() بطباعة تتبع المكدس. استخدم مستوى السجل ERROR. في بيئة الإنتاج، لا تقم بإرجاع تتبع المكدس إلى العميل — قم بتسجيله على الخادم فقط.
س ما هو ترتيب تنفيذ مسار البرمجيات الوسيطة؟
ج نموذج البصل. Chain(h, A, B, C) → يمر الطلب عبر A → B → C → Handler → C → B → A. ترتيب التسجيل = من الطبقة الخارجية إلى الطبقة الداخلية. يقع Recovery في الطبقة الخارجية (يلتقط حالات الذعر التي تطلقها أي طبقة)، ويقع Handler في الطبقة الداخلية (منطق الأعمال).
س كيف يتم إلغاء صلاحية JWT؟
ج JWT لا يعتمد على الحالة — فبمجرد إصداره، لا يمكن إلغاء صلاحيته حتى انتهاء صلاحيته. الحل: (1) رمز قصير الأجل + رمز التحديث (يمكن إلغاء صلاحية رمز التحديث)؛ (2) القائمة السوداء (تخزين معرّفات JWT الملغاة في Redis)؛ (3) رقم الإصدار (زيادة رقم الإصدار عندما يغير المستخدم كلمة مروره؛ فتصبح الرموز القديمة غير صالحة).

📖 ملخص


📝 تمارين

  1. أساسي (مستوى الصعوبة ⭐): قم بتنفيذ برنامج وسيط بسيط للمصادقة. أنشئ نقطة نهاية /api/protected تتطلب أن يتضمن رأس الطلب Authorization: Bearer token123. إذا كان الرمز الصالح صالحًا، فقم بإرجاع {"message": "access granted"}؛ وإلا، فقم بإرجاع خطأ 401.

  2. متقدم (درجة الصعوبة ⭐⭐): تنفيذ نظام مصادقة JWT كامل. المتطلبات: (1) POST /register للتسجيل (تشفير كلمة المرور باستخدام bcrypt)؛ (2) POST /login لتسجيل الدخول (يعيد access_token + refresh_token)؛ (3) برنامج وسيط للتوثيق (Auth middleware) يتحقق من صحة access_token لجميع نقاط النهاية /api/*؛ (4) POST /refresh لاستبدال refresh_token بـ access_token جديد؛ (5) POST /logout لإلغاء refresh_token.

  3. التحدي (الصعوبة ⭐⭐⭐): تنفيذ واجهة برمجة تطبيقات (API) لإدارة المدونة مع تطبيق نموذج RBAC والحد من معدل الاستخدام. المتطلبات: (1) ثلاثة أدوار: المسؤول، والمحرر، والقارئ؛ (2) المسؤول: صلاحيات كاملة؛ (3) المحرر: إنشاء وتحرير وحذف منشوراته الخاصة؛ (4) القارئ: وصول للقراءة فقط؛ (5) لكل دور حدود معدل مختلفة؛ (6) يجب أن تمر جميع نقاط النهاية عبر مسار البرمجيات الوسيطة التالي: الاسترداد → التسجيل → المصادقة → RBAC → RateLimit → المعالج؛ (7) استخدم -race للتحقق من أمان التزامن.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%