لماذا المصادقة في Go ليست مزحة؟
\n\nGo هي اللغة التي يتم اختيارها للسرعة والموثوقية. ولكن حتى أسرع خادم يتحول إلى قرع إذا كان من الممكن سرقة كلمات المرور من خلاله. المصادقة الآمنة في تطبيقات Go ليست مجرد "تثبيت مكتبة ونسيان الأمر". إنها علم كامل: كيفية تخزين كلمات المرور، وكيف لا تفشل في JWT، وكيف تحمي من CSRF عندما يكون كل شيء مشتعلًا.
\n\nفي هذه المقالة، لن نلقي محاضرات حول النظرية. سنكتب كودًا. كودًا حقيقيًا وعمليًا يمكنك إدراجه فورًا في مشروعك. سنتناول ثلاثة أركان للأمان: تجزئة كلمات المرور (bcrypt)، العمل مع رموز JWT، والحماية من تزوير الطلبات عبر المواقع (CSRF).
\n\nتجزئة كلمات المرور بشكل صحيح: لماذا md5 شر
\n\nالخطوة الأولى نحو الأمان هي أبدًا، اسمع، أبدًا لا تخزن كلمات المرور بنص واضح. حتى لو كنت تثق في قاعدة بياناتك. يمكن أن يحدث الاختراق لأي شخص (اسأل Yahoo أو Adobe). التجزئة الصحيحة هي درعك.
\n\nفي Go، الخيار القياسي هو golang.org/x/crypto/bcrypt. لماذا bcrypt بالتحديد؟ إنه مقاوم لهجمات القوة الغاشمة (يتضمن "ملح" ويبطئ العمليات الحسابية). يتم حساب MD5 أو SHA-1 في ميكروثانية. Bcrypt يستغرق مئات الميلي ثانية. بالنسبة للمهاجم، هذه كارثة، وبالنسبة للمستخدم، فهي غير ملحوظة.
مثال: تسجيل مستخدم
\n\npackage main\n\nimport (\n "fmt"\n "golang.org/x/crypto/bcrypt"\n)\n\nfunc hashPassword(password string) (string, error) {\n bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)\n return string(bytes), err\n}\n\nfunc checkPasswordHash(password, hash string) bool {\n err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))\n return err == nil\n}\n\nfunc main() {\n password := "mySecretPass123"\n hash, _ := hashPassword(password)\n\n fmt.Println("كلمة المرور:", password)\n fmt.Println("التجزئة: ", hash)\n\n match := checkPasswordHash(password, hash)\n fmt.Println("متطابقة:", match)\n}\n\nلاحظ: bcrypt.DefaultCost هو 10 تكرارات. للإنتاج، يمكن زيادته إلى 12-14، لكن لا تبالغ وإلا سينهار الخادم تحت الحمل. اختبر على أجهزتك.
التحقق من كلمة المرور عند تسجيل الدخول
\n\nالأمر بسيط: نأخذ كلمة المرور من النموذج، نأخذ التجزئة من قاعدة البيانات، نقارن باستخدام bcrypt.CompareHashAndPassword. إذا أعادت nil، فالمستخدم صحيح. إذا كان هناك خطأ، نرسل 401.
func loginHandler(w http.ResponseWriter, r *http.Request) {\n // نحصل على password من النموذج\n storedHash := getUserHashFromDB(r.FormValue("email"))\n if !checkPasswordHash(r.FormValue("password"), storedHash) {\n http.Error(w, "كلمة مرور غير صحيحة", http.StatusUnauthorized)\n return\n }\n // ننشئ جلسة أو رمزًا\n}\n\nJWT: مصادقة خفيفة مع خدعة
\n\nبعد أن يدخل المستخدم كلمة المرور، يجب إصدار "تصريح" له. إحدى الطرق الشائعة هي JWT (JSON Web Token). هذا رمز مضغوط يحتوي على بيانات المستخدم وتوقيع. لكن الشيطان يكمن في التفاصيل: التنفيذ غير الصحيح لـ JWT قد يجعل تطبيقك عرضة للثغرات.
\n\nالتوليد الصحيح لـ JWT في Go
\n\nنستخدم المكتبة github.com/golang-jwt/jwt/v5. لا تكتب أبدًا حلاً مخصصًا للتشفير - استخدم فقط المكتبات الموثوقة.
import (\n "time"\n "github.com/golang-jwt/jwt/v5"\n)\n\nvar jwtKey = []byte("my_secret_key_which_must_be_long_and_random")\n\ntype Claims struct {\n Username string `json:"username"`\n jwt.RegisteredClaims\n}\n\nfunc generateToken(username string) (string, error) {\n expirationTime := time.Now().Add(15 * time.Minute)\n claims := &Claims{\n Username: username,\n RegisteredClaims: jwt.RegisteredClaims{\n ExpiresAt: jwt.NewNumericDate(expirationTime),\n },\n }\n\n token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n return token.SignedS