RBAC
RBAC (Role-Based Access Control) — rollarga asoslangan kirish nazorati. Bu modelda ruxsatlar foydalanuvchilarga emas, rollarga beriladi. Foydalanuvchi esa bir yoki bir nechta rolga ega bo‘ladi.
Maktabni misol qilib olamiz. "O‘qituvchi" jurnalga baho qo‘ya oladi, "o‘quvchi" faqat o‘z bahosini ko‘radi, "direktor" esa barcha jurnallarni ko‘radi. Maktabga yangi o‘qituvchi kelganda unga bittadan huquq berib chiqilmaydi. Uni shunchaki "o‘qituvchi" deb belgilash kifoya. RBAC ham xuddi shunday ishlaydi.
Uch qatlam
Foydalanuvchi ──→ Rol ──→ Ruxsatlar
Aziza ──→ author ──→ books:read, books:create, books:update
Bobur ──→ reader ──→ books:read
Dilnoza ──→ admin ──→ books:*, users:*
- Ruxsat (permission) — bitta aniq amal:
books:create,users:delete. - Rol (role) — ruxsatlar to‘plami:
reader,author,admin. - Foydalanuvchi — rolga ega bo‘lgan subyekt.
Nima uchun kod rolni emas, ruxsatni tekshirishi kerak? Quyidagi ikki variantni solishtiring:
// ❌ Rol tekshiriladi
if p.Role == "admin" || p.Role == "author" { ... }
// ✅ Ruxsat tekshiriladi
if Can(p.Role, PermBooksCreate) { ... }
Yangi editor roli qo‘shilsa, birinchi variantda barcha iflarni topib, yangilab chiqish kerak bo‘ladi. Ikkinchisida esa faqat rol ta’rifiga bitta qator qo‘shiladi. Handlerlar o‘zgarmaydi.
Ruxsat nomlash
Keng tarqalgan format — resurs:amal:
Nomlar kodda konstanta sifatida e’lon qilinadi. Shunda xato yozilgan nom kompilyatsiya paytidayoq aniqlanadi:
type Permission string
const (
PermBooksRead Permission = "books:read"
PermBooksCreate Permission = "books:create"
PermBooksUpdate Permission = "books:update"
PermBooksDelete Permission = "books:delete"
PermUsersRead Permission = "users:read"
PermUsersManage Permission = "users:manage"
)
Permission alohida tur sifatida e’lon qilindi. Endi Can(role, "books:craete") kabi xato satrni tasodifan berib bo‘lmaydi.
Rollarni e’lon qilish
type Role string
const (
RoleReader Role = "reader"
RoleAuthor Role = "author"
RoleAdmin Role = "admin"
)
var rolePermissions = map[Role][]Permission{
RoleReader: {PermBooksRead},
RoleAuthor: {PermBooksRead, PermBooksCreate, PermBooksUpdate},
RoleAdmin: {
PermBooksRead, PermBooksCreate, PermBooksUpdate, PermBooksDelete,
PermUsersRead, PermUsersManage,
},
}
Barcha qoidalar bitta joyda. "Muallif nima qila oladi?" degan savolga javob topish uchun butun loyihani qidirish shart emas.
Tekshirish funksiyasi
Ruxsatni tez tekshirish uchun ro‘yxat dastur ishga tushganda to‘plam (set) ko‘rinishiga keltiriladi:
var grants = buildGrants(rolePermissions)
func buildGrants(src map[Role][]Permission) map[Role]map[Permission]struct{} {
out := make(map[Role]map[Permission]struct{}, len(src))
for role, perms := range src {
set := make(map[Permission]struct{}, len(perms))
for _, p := range perms {
set[p] = struct{}{}
}
out[role] = set
}
return out
}
func Can(role Role, perm Permission) bool {
_, ok := grants[role][perm]
return ok
}
Can noma’lum rol uchun ham xavfsiz ishlaydi. grants["hacker"] nil map qaytaradi, nil map’dan o‘qish esa ok == false beradi. Demak, noma’lum rol hech qanday ruxsat olmaydi. Bu sukut bo‘yicha rad etish tamoyilining amaldagi ko‘rinishi.
map[Permission]struct{} — Go’da to‘plam yaratishning odatiy usuli. struct{} xotirada joy egallamaydi.
To‘liq misol
"reader" books:read ✅ ruxsat
"reader" books:create ❌ rad
"author" books:create ✅ ruxsat
"author" books:delete ❌ rad
"admin" books:delete ✅ ruxsat
"hacker" books:read ❌ rad
"" books:read ❌ rad
reader+author, books:create: true
Noma’lum hacker roli va bo‘sh rol hech qanday ruxsat olmadi.
Handlerda ishlatish
Auth middleware yozish darsidagi Principal turida foydalanuvchi roli bor. Endi uni ruxsat bilan birga tekshiramiz:
func (h *Handler) deleteBook(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), PermBooksDelete) {
writeError(w, http.StatusForbidden, "forbidden", "bu amal uchun ruxsat yo‘q")
return
}
// ... kitobni o‘chirish
}
Bu tekshiruvni har bir handlerga yozish ham takrorlanishga olib keladi. Middleware orqali ruxsat tekshirish darsida uni middleware’ga chiqaramiz.
Rollarni qayerda saqlash kerak?
Oddiy holat: bitta rol
Har bir foydalanuvchida faqat bitta rol bo‘lsa, users jadvalidagi bitta ustun yetarli:
ALTER TABLE users
ADD COLUMN role TEXT NOT NULL DEFAULT 'reader'
CHECK (role IN ('reader', 'author', 'admin'));
CHECK cheklovi bazaga noma’lum rol yozilishining oldini oladi.
Murakkab holat: bir nechta rol
Foydalanuvchi bir vaqtda bir nechta rolga ega bo‘lishi mumkin bo‘lsa, alohida jadval kerak:
CREATE TABLE user_roles (
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role TEXT NOT NULL,
PRIMARY KEY (user_id, role)
);
Rollar va ruxsatlar ro‘yxatini ham bazada saqlash mumkin. Bunda administrator yangi rollarni kodni o‘zgartirmasdan, admin panel orqali yaratadi. Lekin bu murakkablikni oshiradi. Rollar kam va kamdan-kam o‘zgaradigan bo‘lsa, ularni kodda saqlash sodda va ishonchliroq: har bir o‘zgarish code review’dan o‘tadi va Git tarixida qoladi.
Tokendagi eskirgan rol muammosi
JWT darsida rol tokenning ichiga yozilgan edi. Bu qulay, chunki har bir so‘rovda bazaga murojaat qilinmaydi. Lekin bitta muammo bor:
10:00 Dilnoza admin sifatida login qiladi → token: {role: "admin", exp: 10:15}
10:05 Uni ishdan bo‘shatishdi, roli reader'ga tushirildi
10:05–10:15 Dilnozaning tokenida hali ham "admin" yozilgan
Token muddati tugaguncha eski rol amal qiladi. Bu muammoni hal qilishning uch yo‘li bor:
- Qisqa muddatli token. 15 daqiqalik access token xavfli oynani 15 daqiqa bilan cheklaydi. Aksariyat ilovalar uchun bu maqbul.
- Muhim amallarda rolni bazadan o‘qish. Foydalanuvchilarni boshqarish yoki to‘lov kabi amallarda tokendagi rolga emas, bazadagi joriy rolga qaraladi.
- Tokenga rol yozmaslik. Har bir so‘rovda rol bazadan yoki keshdan o‘qiladi. Bu eng aniq usul, lekin har bir so‘rov qo‘shimcha murojaat talab qiladi.
Ko‘p loyihalarda birinchi va ikkinchi usul birgalikda qo‘llanadi.
Xulosa
- RBAC’da ruxsatlar rollarga, rollar esa foydalanuvchilarga beriladi.
- Kod rolni emas, ruxsatni tekshiradi. Shunda yangi rol qo‘shish handlerlarni o‘zgartirmaydi.
- Ruxsat va rollar alohida turlar va konstantalar sifatida e’lon qilinadi.
- Barcha qoidalar bitta joyda saqlanadi. Noma’lum rol hech qanday ruxsat olmaydi.
- Tokendagi rol eskirishi mumkin. Muhim amallarda uni bazadan tekshiring.
RBAC "kitob o‘chirish mumkinmi?" degan savolga javob beradi. Lekin u "aynan shu kitobni o‘chirish mumkinmi?" degan savolga javob bermaydi. Keyingi darsda resursga egalikni tekshirishni o‘rganamiz.