Middleware orqali ruxsat tekshirish
RBAC darsida ruxsat tekshiruvi handler ichida yozilgan edi:
func (h *Handler) deleteBook(w http.ResponseWriter, r *http.Request) {
p, ok := PrincipalFrom(r.Context())
if !ok {
writeError(w, http.StatusUnauthorized, ...)
return
}
if !Can(Role(p.Role), PermBooksDelete) {
writeError(w, http.StatusForbidden, ...)
return
}
// ... asosiy ish
}
O‘nta handlerda bu kod o‘n marta takrorlanadi. Bundan ham yomoni, handlerning boshida uni yozishni unutish oson. Kod ishlaydi, testlar o‘tadi, endpoint esa hammaga ochiq qoladi.
Bu darsda endpoint darajasidagi ruxsat tekshiruvini middleware’ga chiqaramiz. Natijada har bir endpointning himoyasi route ro‘yxatida, bir qarashda ko‘rinib turadi.
RequirePermission middleware
func RequirePermission(perm Permission) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p, ok := PrincipalFrom(r.Context())
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized", "tizimga kiring")
return
}
if !Can(Role(p.Role), perm) {
writeError(w, http.StatusForbidden, "forbidden", "bu amal uchun ruxsat yo‘q")
return
}
next.ServeHTTP(w, r)
})
}
}
Middleware parametr qabul qiladi, ya’ni qaysi ruxsat kerakligini. Shu sababli u middleware qaytaradigan funksiya ko‘rinishida yozilgan. Bu naqsh Middleware va logging asoslari darsida tushuntirilgan.
PrincipalFrom false qaytarsa, 401 beriladi. Bu holat odatda auth middleware ulanmay qolganini bildiradi. Lekin xavfsiz tomonga og‘ish kerak: foydalanuvchi noma’lum bo‘lsa, ruxsat berilmaydi.
Auth va ruxsat zanjiri
Ruxsat middleware’i auth middleware’dan keyin ishlashi kerak. Chunki u auth middleware contextga joylagan Principaldan foydalanadi:
Buni har bir route uchun qo‘lda yozish noqulay:
mux.Handle("DELETE /books/{id}",
auth.Require(RequirePermission(PermBooksDelete)(http.HandlerFunc(h.deleteBook))))
Kichik yordamchi funksiya bu yozuvni ancha soddalashtiradi:
// protect handlerni autentifikatsiya va berilgan ruxsat tekshiruvi bilan o‘raydi.
func (a *Auth) protect(perm Permission, h http.HandlerFunc) http.Handler {
return a.Require(RequirePermission(perm)(h))
}
Endi route ro‘yxati o‘qish uchun qulay:
mux.HandleFunc("GET /books", h.listBooks) // ochiq
mux.Handle("POST /books", auth.protect(PermBooksCreate, h.createBook))
mux.Handle("PUT /books/{id}", auth.protect(PermBooksUpdate, h.updateBook))
mux.Handle("DELETE /books/{id}", auth.protect(PermBooksDelete, h.deleteBook))
mux.Handle("GET /admin/users", auth.protect(PermUsersRead, h.listUsers))
Kodni ko‘rib chiqayotgan dasturchi (code reviewer) har bir endpoint qanday himoyalanganini darhol ko‘radi. Himoyasiz qolgan endpoint esa ko‘zga tashlanib turadi.
RequireRole — qachon kerak?
Ba’zan ruxsat emas, rolning o‘zini tekshirish qulayroq. Masalan, butun admin paneli uchun:
func RequireRole(roles ...Role) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p, ok := PrincipalFrom(r.Context())
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized", "tizimga kiring")
return
}
if !slices.Contains(roles, Role(p.Role)) {
writeError(w, http.StatusForbidden, "forbidden", "bu bo‘lim uchun ruxsat yo‘q")
return
}
next.ServeHTTP(w, r)
})
}
}
Lekin RBAC darsida aytilganidek, iloji boricha ruxsatni tekshirgan ma’qul. RequireRole faqat haqiqatan ham rolga bog‘liq holatlarda ishlatiladi. Masalan, "faqat admin rolidagilar admin paneliga kira oladi".
Endpointlarni guruhlash
Admin panelida o‘nlab endpoint bo‘lishi mumkin. Har biriga alohida himoya qo‘yish o‘rniga, ularni alohida mux’ga yig‘ib, butun guruhni bitta middleware bilan o‘rash mumkin:
admin := http.NewServeMux()
admin.HandleFunc("GET /admin/users", h.listUsers)
admin.HandleFunc("DELETE /admin/users/{id}", h.deleteUser)
admin.HandleFunc("GET /admin/stats", h.stats)
mux.Handle("/admin/", auth.Require(RequireRole(RoleAdmin)(admin)))
- Asosiy mux’dagi
/admin/patterni/admin/bilan boshlanadigan barcha so‘rovlarni ushlaydi. - So‘rov avval auth, keyin rol tekshiruvidan o‘tadi va ichki
adminmux’iga yetib boradi. - Ichki mux’dagi patternlar to‘liq yo‘l bilan yozilgan:
/admin/users,/usersemas. Chunki so‘rov yo‘li o‘zgarmasdan uzatiladi. - Admin guruhiga yangi endpoint qo‘shilsa, u avtomatik himoyalangan bo‘ladi. Himoyani unutish imkoni yo‘q.
gorilla/mux va Gin kabi routerlarda guruhlash uchun maxsus imkoniyatlar bor. Ular Subroutes va Gin arxitekturasi darslarida ko‘riladi.
To‘liq misol
Misolni o‘zi ichida sinab ko‘rish uchun net/http/httptest paketi ishlatiladi. U haqiqiy server ishga tushirmasdan, router’ga so‘rov yuborib, javobni tekshirish imkonini beradi.
JWT tekshiruvi Auth middleware yozish darsida to‘liq ko‘rilgan. Bu yerda diqqatni ruxsatlarga qaratish uchun uning o‘rniga soddalashtirilgan auth middleware ishlatiladi. U tokenni tayyor ro‘yxatdan qidiradi.
| main.go | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | |
GET /books → 200 {"message":"kitoblar ro‘yxati"}
POST /books → 401 {"error":{"code":"unauthorized","message":"tizimga kiring"}}
POST /books t-reader → 403 {"error":{"code":"forbidden","message":"bu amal uchun ruxsat yo‘q"}}
POST /books t-author → 200 {"message":"kitob yaratildi"}
DELETE /books/5 t-author → 403 {"error":{"code":"forbidden","message":"bu amal uchun ruxsat yo‘q"}}
DELETE /books/5 t-admin → 200 {"message":"kitob o‘chirildi"}
GET /admin/stats t-author → 403 {"error":{"code":"forbidden","message":"bu bo‘lim uchun ruxsat yo‘q"}}
GET /admin/stats t-admin → 200 {"message":"statistika"}
GET /admin/users soxta → 401 {"error":{"code":"unauthorized","message":"tizimga kiring"}}
Natijalar kutilganidek:
- token bo‘lmasa yoki soxta bo‘lsa —
401; - token bor, lekin ruxsat yetarli emas —
403; - ruxsat bor — handler ishlaydi.
Bu jadval shaklidagi sinov haqiqiy test sifatida ham yozilishi mumkin. Har bir endpoint uchun "kim kira oladi, kim kira olmaydi" ro‘yxatini test ko‘rinishida saqlash avtorizatsiyadagi xatolarni kod production’ga chiqishidan oldin ushlaydi.
Middleware nimani tekshira olmaydi?
Middleware handler ishga tushishidan oldin ishlaydi. U so‘rov haqida faqat yo‘l, metod, sarlavhalar va tokenni biladi. Resursning o‘zi haqida esa hech narsa bilmaydi.
DELETE /books/42
✅ middleware biladi: foydalanuvchida books:delete ruxsati bormi?
❌ middleware bilmaydi: 42-kitob kimniki?
42-kitobning egasini bilish uchun uni bazadan o‘qish kerak. Bu ish esa handler yoki service qatlamida bajariladi. Shuning uchun ruxsat tekshiruvi ikki qatlamda bo‘ladi:
| Qatlam | Nimani tekshiradi | Qayerda |
|---|---|---|
| Endpoint | Amal turi: books:delete |
Middleware |
| Resurs | Egalik: 42-kitob unikimi? | Service (oldingi dars) |
Ikkinchisi birinchisining o‘rnini bosmaydi va aksincha.
Xulosa
- Endpoint darajasidagi ruxsat tekshiruvi middleware’ga chiqariladi. Bu takrorlanishni va tekshiruvni unutish xavfini kamaytiradi.
- Ruxsat middleware’i auth middleware’dan keyin ishlaydi va contextdagi
Principaldan foydalanadi. protectkabi yordamchi funksiya route ro‘yxatini o‘qishni osonlashtiradi.- Endpointlar guruhini alohida mux’ga yig‘ib, butun guruhni bitta middleware bilan himoyalash mumkin.
- Middleware resursga egalikni tekshira olmaydi. Bu tekshiruv service qatlamida qoladi.
httptestyordamida router’ni haqiqiy serversiz sinash mumkin.
Keyingi darsda 401, 403 va 500 status kodlarini qachon qaytarish kerakligini aniqlab olamiz.