Tarkibga o'tish

Security headers & CORS

Brauzer — server va foydalanuvchi o‘rtasidagi vositachi. Server javob sarlavhalari orqali brauzerga ma’lumotni qanday qayta ishlash kerakligini aytadi: "bu javobni boshqa saytga ramka ichida ko‘rsatma", "faqat HTTPS orqali ulan", "Content-Typeni taxmin qilma". Bu sarlavhalar xavfsizlik sarlavhalari (security headers) deyiladi.

Ikkinchi mavzu — CORS. U brauzerga boshqa domendagi sahifaning sizning API’ngizdan ma’lumot o‘qishiga ruxsat bor-yo‘qligini aytadi.

1. Xavfsizlik sarlavhalari

Asosiy sarlavhalar

Sarlavha Qiymat (API uchun) Nimadan himoya qiladi
X-Content-Type-Options nosniff Brauzer Content-Typeni taxmin qilmaydi. JSON HTML sifatida ochilib ketmaydi
Content-Security-Policy default-src 'none'; frame-ancestors 'none' XSS, begona resurslarni yuklash, ramkaga joylash
X-Frame-Options DENY Clickjacking. Eski brauzerlar uchun frame-ancestorsning o‘rnini bosadi
Referrer-Policy no-referrer URL’dagi maxfiy ma’lumot boshqa saytlarga Referer sarlavhasi orqali sizib chiqmaydi
Strict-Transport-Security max-age=63072000; includeSubDomains HTTPS’dan HTTP’ga tushirish hujumlari. HTTP → HTTPS darsida ko‘rilgan
Cache-Control no-store (shaxsiy ma’lumotlar uchun) Javob umumiy keshlarda va brauzer tarixida saqlanib qolmaydi
Cross-Origin-Resource-Policy same-origin Boshqa saytlar javobni <img> yoki <script> orqali yuklay olmaydi

Clickjacking — hujumchi sizning sahifangizni ko‘rinmas ramka (<iframe>) ichida ochadi va ustiga o‘z tugmalarini joylashtiradi. Foydalanuvchi "yutuqni olish" tugmasini bosyapman deb o‘ylaydi, aslida esa ramka ichidagi "akkauntni o‘chirish" tugmasini bosadi. frame-ancestors 'none' va X-Frame-Options: DENY sahifani ramkaga joylashni taqiqlaydi.

HTML sahifa qaytaradigan server uchun

Faqat JSON qaytaradigan API uchun eng qat’iy CSP yetarli. HTML sahifalar uchun esa CSP ancha murakkabroq bo‘ladi, chunki unda skriptlar, stillar va rasmlar manbalari ko‘rsatiladi:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

CSP’ni yoqishdan oldin uni Content-Security-Policy-Report-Only sarlavhasi bilan sinab ko‘ring. Bu rejimda brauzer hech narsani bloklamaydi, faqat qoidalar buzilgan holatlar haqida konsolda xabar beradi.

Middleware

func SecurityHeaders(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        h := w.Header()
        h.Set("X-Content-Type-Options", "nosniff")
        h.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'")
        h.Set("X-Frame-Options", "DENY")
        h.Set("Referrer-Policy", "no-referrer")
        h.Set("Cross-Origin-Resource-Policy", "same-origin")
        if r.TLS != nil {
            h.Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
        }
        next.ServeHTTP(w, r)
    })
}

Middleware router’ni tashqaridan o‘rashi kerak. Shunda sarlavhalar 404 va boshqa xato javoblariga ham qo‘shiladi.

HSTS faqat HTTPS ulanishida qo‘shiladi. Proxy ortida esa buning o‘rniga ishonchli proxy’dan kelgan X-Forwarded-Proto tekshiriladi.

Keraksiz ma’lumotni yashirish

Go serveri Server yoki X-Powered-By kabi sarlavhalarni qo‘shmaydi. Bu yaxshi: hujumchi qaysi texnologiya ishlatilayotganini javobdan bila olmaydi. Proxy’lar, masalan nginx, bunday sarlavhani qo‘shishi mumkin. Ularni proxy sozlamalarida o‘chiring.

2. CORS

Same-origin siyosati

Brauzerda same-origin policy (bir manba siyosati) amal qiladi. Bir manbadagi sahifa boshqa manbadagi javobni JavaScript orqali o‘qiy olmaydi.

Manba (origin) uch qismdan iborat: sxema, host va port.

URL https://app.example.uz bilan bir manbami?
https://app.example.uz/books ✅ ha
http://app.example.uz ❌ sxema boshqa
https://api.example.uz ❌ host boshqa
https://app.example.uz:8443 ❌ port boshqa

Bu siyosat foydalanuvchini himoya qiladi. Siz bank saytiga kirgan bo‘lsangiz, boshqa oynada ochilgan zararli sayt JavaScript orqali bank API’sidan hisobingiz qoldig‘ini o‘qiy olmaydi.

Lekin ko‘pincha frontend va API turli manbalarda joylashadi: https://app.example.uz va https://api.example.uz. Bu holatda API brauzerga "bu sahifaga mening javoblarimni o‘qishga ruxsat ber" deyishi kerak. CORS (Cross-Origin Resource Sharing) aynan shu mexanizm.

Info

CORS serverni himoya qilmaydi. U brauzerdagi same-origin cheklovini yumshatadigan mexanizm. curl, mobil ilova yoki boshqa server CORS qoidalariga umuman qaramaydi. Shuning uchun CORS autentifikatsiya va avtorizatsiyaning o‘rnini bosa olmaydi.

Oddiy so‘rovlar

GET yoki oddiy forma POST so‘rovi uchun brauzer so‘rovni yuboradi va unga Origin sarlavhasini qo‘shadi:

GET /books HTTP/1.1
Origin: https://app.example.uz

Server ruxsat bersa, javobga sarlavha qo‘shadi:

Access-Control-Allow-Origin: https://app.example.uz

Sarlavha bo‘lmasa, brauzer javobni oladi, lekin uni JavaScript’ga bermaydi. Konsolda CORS xatosi chiqadi.

Preflight so‘rov

PUT, DELETE, Content-Type: application/json yoki Authorization sarlavhasi bor so‘rovlardan oldin brauzer ruxsat so‘raydi. Bu preflight so‘rov deb ataladi:

OPTIONS /books/42 HTTP/1.1
Origin: https://app.example.uz
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization, content-type

Server ruxsat bersa:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.uz
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 600

Shundan keyingina brauzer asosiy DELETE so‘rovini yuboradi. Access-Control-Max-Age preflight natijasini 10 daqiqaga keshlaydi, shuning uchun har bir so‘rovdan oldin qayta ruxsat so‘ralmaydi.

Brauzer cookie’larni boshqa manbaga faqat frontend credentials: "include" bilan so‘rov yuborganda va server bunga aniq ruxsat berganda qo‘shadi:

Access-Control-Allow-Credentials: true

Bu holatda muhim qoida bor: Access-Control-Allow-Origin: * ishlamaydi. Aniq manba ko‘rsatilishi shart.

Keng tarqalgan xatolar

// ❌ Har qanday manbaga cookie bilan ruxsat — CSRF va ma’lumot o‘g‘irlashga yo‘l
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
w.Header().Set("Access-Control-Allow-Credentials", "true")

Bu kod so‘rov kelgan manbani tekshirmasdan qaytaradi. Natijada istalgan sayt foydalanuvchi nomidan API’dan ma’lumot o‘qiy oladi. Bu same-origin siyosatini butunlay o‘chirib qo‘yish bilan barobar.

// ❌ strings.Contains bilan tekshirish
if strings.Contains(origin, "example.uz") { ... }
// https://example.uz.evil.com ham o‘tib ketadi

To‘g‘ri yo‘l — aniq manbalar ro‘yxati bilan to‘liq solishtirish.

CORS middleware

type CORS struct {
    allowed map[string]bool
}

func NewCORS(origins ...string) *CORS {
    c := &CORS{allowed: make(map[string]bool)}
    for _, o := range origins {
        c.allowed[o] = true
    }
    return c
}

func (c *CORS) Handler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        origin := r.Header.Get("Origin")
        w.Header().Add("Vary", "Origin")

        if origin == "" || !c.allowed[origin] {
            if r.Method == http.MethodOptions && origin != "" {
                w.WriteHeader(http.StatusForbidden) // ruxsat etilmagan manbadan preflight
                return
            }
            next.ServeHTTP(w, r) // CORS sarlavhalarisiz: brauzer javobni bloklaydi
            return
        }

        h := w.Header()
        h.Set("Access-Control-Allow-Origin", origin)
        h.Set("Access-Control-Allow-Credentials", "true")
        h.Set("Access-Control-Expose-Headers", "RateLimit-Remaining, X-Request-Id")

        // Preflight so‘rov
        if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
            h.Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE")
            h.Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
            h.Set("Access-Control-Max-Age", "600")
            w.WriteHeader(http.StatusNoContent)
            return
        }

        next.ServeHTTP(w, r)
    })
}

Muhim jihatlar:

  • Vary: Origin — javob Origin sarlavhasiga bog‘liq ekanini keshlarga bildiradi. Busiz CDN bitta manba uchun tayyorlangan javobni boshqa manbaga ham berib yuborishi mumkin.
  • Preflight router’ga yetkazilmaydi. Middleware OPTIONS so‘roviga o‘zi javob beradi. Shuning uchun har bir marshrut uchun OPTIONS handler yozish shart emas. Bu gorilla/mux middleware darsida aytilgan muammoni ham hal qiladi.
  • Access-Control-Expose-Headers — standart holatda JavaScript faqat bir nechta oddiy javob sarlavhalarini o‘qiy oladi. RateLimit-Remaining kabi maxsus sarlavhalarni ko‘rsatish uchun ular shu ro‘yxatga qo‘shiladi.
  • Middleware router’ni tashqaridan o‘raydi.

To‘liq misol

main.go
package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
)

func SecurityHeaders(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        h := w.Header()
        h.Set("X-Content-Type-Options", "nosniff")
        h.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'")
        h.Set("X-Frame-Options", "DENY")
        h.Set("Referrer-Policy", "no-referrer")
        h.Set("Cross-Origin-Resource-Policy", "same-origin")
        if r.TLS != nil {
            h.Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
        }
        next.ServeHTTP(w, r)
    })
}

type CORS struct{ allowed map[string]bool }

func NewCORS(origins ...string) *CORS {
    c := &CORS{allowed: make(map[string]bool)}
    for _, o := range origins {
        c.allowed[o] = true
    }
    return c
}

func (c *CORS) Handler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        origin := r.Header.Get("Origin")
        w.Header().Add("Vary", "Origin")

        if origin == "" || !c.allowed[origin] {
            if r.Method == http.MethodOptions && origin != "" {
                w.WriteHeader(http.StatusForbidden)
                return
            }
            next.ServeHTTP(w, r)
            return
        }

        h := w.Header()
        h.Set("Access-Control-Allow-Origin", origin)
        h.Set("Access-Control-Allow-Credentials", "true")
        h.Set("Access-Control-Expose-Headers", "RateLimit-Remaining, X-Request-Id")

        if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
            h.Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE")
            h.Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
            h.Set("Access-Control-Max-Age", "600")
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func show(h http.Handler, method, origin string, extra map[string]string) {
    req := httptest.NewRequest(method, "/books/42", nil)
    if origin != "" {
        req.Header.Set("Origin", origin)
    }
    for k, v := range extra {
        req.Header.Set(k, v)
    }
    rec := httptest.NewRecorder()
    h.ServeHTTP(rec, req)

    fmt.Printf("%s %s → %d\n", method, origin, rec.Code)
    for _, k := range []string{"Access-Control-Allow-Origin", "Access-Control-Allow-Methods", "Access-Control-Max-Age"} {
        if v := rec.Header().Get(k); v != "" {
            fmt.Printf("   %s: %s\n", k, v)
        }
    }
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /books/{id}", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintln(w, `{"id":42,"title":"Sarob"}`)
    })
    mux.HandleFunc("DELETE /books/{id}", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusNoContent)
    })

    cors := NewCORS("https://app.example.uz", "http://localhost:3000")
    handler := SecurityHeaders(cors.Handler(mux))

    // 1. Xavfsizlik sarlavhalari
    rec := httptest.NewRecorder()
    handler.ServeHTTP(rec, httptest.NewRequest("GET", "/books/42", nil))
    fmt.Println("Xavfsizlik sarlavhalari:")
    for _, k := range []string{"X-Content-Type-Options", "Content-Security-Policy", "X-Frame-Options", "Referrer-Policy"} {
        fmt.Printf("   %s: %s\n", k, rec.Header().Get(k))
    }
    fmt.Println()

    // 2. CORS holatlari
    show(handler, "GET", "https://app.example.uz", nil)
    show(handler, "OPTIONS", "https://app.example.uz", map[string]string{
        "Access-Control-Request-Method":  "DELETE",
        "Access-Control-Request-Headers": "authorization",
    })
    show(handler, "OPTIONS", "https://evil.example", map[string]string{
        "Access-Control-Request-Method": "DELETE",
    })
    show(handler, "GET", "https://app.example.uz.evil.com", nil)
    show(handler, "GET", "", nil)
}
Xavfsizlik sarlavhalari:
   X-Content-Type-Options: nosniff
   Content-Security-Policy: default-src 'none'; frame-ancestors 'none'
   X-Frame-Options: DENY
   Referrer-Policy: no-referrer

GET https://app.example.uz → 200
   Access-Control-Allow-Origin: https://app.example.uz
OPTIONS https://app.example.uz → 204
   Access-Control-Allow-Origin: https://app.example.uz
   Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
   Access-Control-Max-Age: 600
OPTIONS https://evil.example → 403
GET https://app.example.uz.evil.com → 200
GET  → 200

Natijalarni tahlil qilamiz:

  • Ruxsat etilgan frontend Access-Control-Allow-Origin sarlavhasini oldi va preflight so‘rovi 204 bilan tasdiqlandi.
  • evil.example manbasidan kelgan preflight 403 oldi. Brauzer asosiy DELETE so‘rovini umuman yubormaydi.
  • app.example.uz.evil.com manbasi uchun javob qaytdi, lekin CORS sarlavhasiz. Brauzer bu javobni sahifaga bermaydi. To‘liq solishtirish soxta subdomen hiylasini to‘xtatdi.
  • Origin sarlavhasisiz so‘rov, ya’ni curl yoki mobil ilova, odatdagidek ishladi. Uni esa autentifikatsiya va avtorizatsiya himoya qiladi.

Tayyor kutubxona

CORS’ning ko‘plab nozik holatlarini hisobga oladigan tayyor kutubxona ham bor: github.com/rs/cors. U net/http bilan mos keladi va Gin hamda boshqa framework’lar uchun adapterlarga ega:

c := cors.New(cors.Options{
    AllowedOrigins:   []string{"https://app.example.uz"},
    AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE"},
    AllowedHeaders:   []string{"Authorization", "Content-Type"},
    AllowCredentials: true,
    MaxAge:           600,
})
handler := c.Handler(mux)

Xulosa

  • Xavfsizlik sarlavhalari brauzerga javobni xavfsiz qayta ishlashni buyuradi. API uchun asosiylari: nosniff, qat’iy CSP, frame-ancestors, Referrer-Policy va HSTS.
  • Sarlavhalar middleware orqali, router’ni tashqaridan o‘rab qo‘shiladi.
  • Same-origin siyosati boshqa manbadagi javobni o‘qishni taqiqlaydi. CORS esa bu cheklovni ongli ravishda yumshatadi.
  • CORS serverni himoya qilmaydi. U faqat brauzer uchun mo‘ljallangan qoida.
  • Manbalar aniq ro‘yxat bilan to‘liq solishtiriladi. Originni tekshirmasdan qaytarish va Contains bilan tekshirish xavfli.
  • Cookie bilan ishlaganda * ishlatib bo‘lmaydi. Vary: Origin sarlavhasini unutmang.

Shu bilan kursning API bo‘limi yakunlandi. Unda HTTP server asoslaridan boshlab JSON, ma’lumotlar bazasi, autentifikatsiya, avtorizatsiya, routerlar, Gin, HTTPS va xavfsizlikkacha bo‘lgan mavzular ko‘rib chiqildi. Endi bu bilimlarni o‘z loyihangizda birlashtirib, to‘liq API yaratishingiz mumkin.