HMAC bilan imzolangan tokenlar yordamida taklif havolalarini yaratish

HMAC bilan imzolangan tokenlar yordamida taklif havolalarini yaratish

1. Avval bir qatorda

HMAC server taklif havolasini berganda va server uni tekshirganda ishlatiladigan umumiy maxfiy kalitdir. Frontend imzoni hisoblamaydi va kalit har bir havola uchun alohida bo‘lmaydi. Har bir muhit uchun bitta kalit mavjud, havolalar bo‘yicha farq qiladigan narsa esa pool ID va amal qilish muddatini o‘z ichiga olgan payloaddir.

Ushbu maqolada workforce pool taklif havolalariga amal qilish muddatlarini qo‘shish jarayonida nima uchun HMAC-SHA256 bilan imzolangan tokenlarni tanlaganimiz, dastlab nimani noto‘g‘ri tushunganim, kodni amalda qanday integratsiya qilganimiz va deployment sozlamalarini qanday ajratganimiz, shuningdek, bir xil xatolarni takrorlamaslik uchun amaliy eslatmalar bayon qilinadi. Nazariy tushuntirishlarga e’tibor qaratish o‘rniga, o‘sha paytda nima uchun aynan shu tanlovlarni qilganimiz va nimada xato qilganimizga urg‘u beriladi.

2. Nima uchun ushbu texnologiyadan foydalanishga keldik

2.1 Mahsulot talablari

Jamoa rahbarlari odamlarni workforce pool’ga taklif qilishda umumiy havolalardan foydalanadi. Mavjud havolalarda query ichida faqat pool ID bor edi. Havolani olgan xodim ilovaga kirganda, shu pool’ga taklif yaratilardi. Muammo shundaki, amal qilish muddati yo‘q edi. Havola tarqalib ketgach, uni cheksiz muddat davomida qayta ochish mumkin edi va nazariy jihatdan URL’ni bilgan har qanday kishi boshqa pool’ni ko‘rsatish uchun pool ID’ni o‘zgartira olardi.

Mahsulot jamoasi taklif oynasi har safar ochilganda yangi havola yaratilishini, shu bilan birga bir nechta xodim ushbu havoladan birgalikda foydalana olishini so‘radi. Havolaning amal qilish muddati bo‘lishi, bu muddat client’da yashirilmasligi va server tekshirish vaqtida havolani rad etishi kerak edi. Muddati o‘tgan takliflarni faqat ro‘yxat ekranida yashirish yetarli emas edi.

2.2 Muqobillarni ko‘rib chiqqach, nima uchun buni tanladik

Dastlab uchta variantni yonma-yon ko‘rib chiqdik.

  • Mavjud umumiy havolani o‘zgarishsiz qoldirib, faqat “Iltimos, bir necha kun ichida kiring” degan bildirishnoma yozish.

  • Pool ID va amal qilish muddatini faqat Base64 yordamida URL ichiga joylashtirish. Imzo yo‘q.

  • Pool ID va amal qilish muddatini payload sifatida ishlatish, server maxfiy kalitidan foydalanib HMAC-SHA256 tegini biriktirish va ularni bitta tokenga birlashtirish.

Birinchi variantni amalga oshirish eng oson edi. Biroq talab “amal qilish muddatini tekshirish” bo‘lgani uchun, faqat bildirishnoma bunga javob bera olmasdi. Ikkinchi variantda URL dekodlansa, kimdir amal qilish muddatini uzaytirishi yoki pool ID’ni o‘zgartirishi mumkin edi. Bu qanday o‘zgartirishni bilgan har qanday kishi o‘zgartira oladigan tuzilma edi. Uchinchi variantda esa aynan bir xil tegni faqat kalitga ega server yarata oladi, shuning uchun URL o‘zgartirilsa, tekshiruv muvaffaqiyatsiz tugaydi. Bu bir nechta odam havolani baham ko‘radigan modelga ham mos keladi. Sessiya cookie’lari yoki bir martalik login kodlari umumiy havolalar uchun mos emas edi.

Shuning uchun HMAC bilan imzolangan tokenni tanladik. JWT’dan foydalanmadik, chunki bu token login subyektini aniqlashdan ko‘ra, “shu pool uchun shu vaqtgacha amal qiladigan taklif” degan qisqa claim’ni o‘z ichiga olishi kerak edi. Faqat ikkita claim bo‘lgani sababli, qo‘lda kodlangan payload’ni o‘qish osonroq edi.

3. Dastlab nimani o‘ylaganim va xato nimada bo‘lgani

Texnologiyani nomi bilan bilish va uni deployment orqali to‘liq integratsiya qilish o‘rtasida katta farq bor edi. Quyidagilar o‘sha paytdagi noto‘g‘ri tushunchalarimdir.

3.1 Yangi umumiy HmacUtil yaratishimiz kerak

Dastlab standart Base64’dan foydalanadigan yana bir umumiy utilita qo‘shsak bo‘ladi, deb o‘yladim. Kodni ko‘rib chiqqanimda, HMAC-SHA256 allaqachon domain service’da mavjudligini va uni URL’ga joylashtirish uchun URL-safe Base64 (padding’siz) allaqachon ishlatilayotganini aniqladim. Standart Base64’dagi +, / va = belgilar query string’larni buzishi mumkin. Yangi utilita yaratishdan oldin, avvalo ishlatilayotgan kodlash usuliga moslashishimiz kerak edi.

3.2 Secret’da faqat kalit nomini ko‘rsatsak, ilova kalitni avtomatik o‘qiydi

Kubernetes Secret obyektida hmac-secret kabi nom yaratsak, Spring uni avtomatik o‘qiydi, deb o‘yladim. Aslida application yml muhit o‘zgaruvchisi nomini o‘qiydi va bu nom pod env o‘zgaruvchisi sifatida ochiq taqdim etilishi kerak. Secret kaliti yml placeholder’idan hatto bitta belgi bilan farq qilsa ham, ilova standart qiymatni yoki bo‘sh qiymatni ko‘radi. “Secret yaratish” va “pod’ga shu nomdagi o‘zgaruvchini yetkazish” — alohida narsalardir.

3.3 Imzoni frontend hisoblaydi

Havola yaratish ekrani frontend’da bo‘lgani uchun, frontend kalit yordamida HMAC’ni ishga tushirib, uni API’ga yuboradi, deb o‘yladim. Bu maxfiy kalitni bundle ichiga joylashtirib qo‘ygan bo‘lardi. Amaldagi oqim buning teskarisidir. Jamoa rahbari taklif havolasini so‘raganda, server token qatorini qaytaradi, frontend esa faqat shu qatorni query’ga qo‘shadi. Xodim havola orqali kirganda, aynan shu qator o‘zgartirilmagan holda registration API’ga yuboriladi. Tekshiruvni ham server amalga oshiradi.

3.4 HMAC kaliti har bir havola uchun alohida

Agar kalit har bir taklif uchun o‘zgarsa, kalitlarni saqlash va boshqarishning amaliy usuli bo‘lmas edi. Har bir muhit — development, staging va production — uchun bitta kalit mavjud, har bir havola bo‘yicha farq qiladigan narsa esa payloaddir. Turli pool ID’lar va turli amal qilish muddatlari bir xil kalit bilan imzolanadi.

3.5 Staging’da ConfigMap ham kerak

Agar yml’da standart qiymat bo‘lsa, development va staging faqat deployment orqali ishlashi mumkin. Standart qiymatni almashtirish uchun Secret’dan faqat production’da foydalanamiz. Faqat shu funksiya uchun avval staging ConfigMap’ini so‘rash shart emas edi. Biroq production va development bir xil kalitdan foydalanmasligi kerak.

4. Bu yerda HMAC nima qiladi

HMAC-SHA256 xabar va maxfiy kalitdan qat’iy uzunlikdagi teg yaratadi. Faqat kalitga ega tomon aynan bir xil tegni qayta yarata oladi. Shuning uchun kimdir URL’dagi amal qilish muddatini bir kunga uzaytirsa yoki pool ID’ni o‘zgartirsa ham, teg mos kelmaganda server uni rad etadi. Bu shifrlash emas. Payload dekodlanganda ko‘rinadi. Ushbu texnologiya mazmun ko‘rinishining oldini olmaydi; u ko‘rinib turgan mazmunning o‘zboshimchalik bilan o‘zgartirilishiga yo‘l qo‘ymaydi.

Konseptual jihatdan, ushbu loyihadagi token ikki qismdan iborat: payload.signature. Payload pool ID va amal qilish muddatini kodlash, so‘ng natijani URL-safe Base64 yordamida kodlash orqali yaratiladigan qatordir; signature esa shu payload qatori uchun HMAC-SHA256 natijasini xuddi shu usulda kodlash orqali yaratiladigan qatordir. Tekshiruv vaqtida olingan payload yordamida imzoni qayta hisoblaymiz va uni taqqoslaymiz, keyin amal qilish muddatini tekshiramiz. Avval imzoni tekshirish qalbakilashtirilgan amal qilish muddatiga hech qachon ishonmasligimizni ta’minlaydi.

5. Amalga oshirish jarayoni

5.1 Konfiguratsiya o‘qiydigan nom

Spring konfiguratsiyasi taxminan quyidagicha bo‘ladi. Ikki nuqtadan keyingi qism pod’da muhit o‘zgaruvchisi bo‘lmaganida ishlatiladigan standart qiymatdir. Production’da bu standart qiymat ishlatilmaydi va pod’ga xuddi shu nomdagi muhit o‘zgaruvchisi kiritiladi, deb hisoblaymiz.

banjang:
  contact-pool:
    invite:
      hmac-secret: ${BANJANG_CONTACT_POOL_INVITE_HMAC_SECRET:dev-only-not-for-prod}
      expire-hours: ${BANJANG_CONTACT_POOL_INVITE_EXPIRE_HOURS:72}

Bu yerda ${name:default value} sintaksisini ko‘pincha chalkashtirib yuborardim. Kubernetes yml’ni bevosita o‘zgartirmaydi. Ilova ishga tushganda, agar BANJANG_CONTACT_POOL_INVITE_HMAC_SECRET muhit o‘zgaruvchisi mavjud bo‘lsa, shu qiymat ishlatiladi; aks holda, ikki nuqtadan keyingi qiymat ishlatiladi. Mapping’ni sodda saqlash uchun Secret obyektining kalit nomi ham ushbu muhit o‘zgaruvchisi nomi bilan bir xil bo‘lishi kerak.

5.2 Server tokenni qayerda yaratadi (konseptual kod)

Quyidagi kod tegishli holat repository’da mavjud emas, degan taxminga asoslangan konseptual koddir. Haqiqiy class va package nomlari jamoa kodidan farq qilishi mumkin. Muhimi, kodlash URL-safe va padding’siz bo‘lishi hamda imzolash obyekti “allaqachon kodlangan payload qatori” bo‘lishidir.

public String issue(String poolId, Instant expiresAt) {
    String payloadJson = "{"p":"" + poolId + "","e":" + expiresAt.getEpochSecond() + "}";
    String payload = base64UrlNoPad(payloadJson.getBytes(StandardCharsets.UTF_8));
    
Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
    String signature = base64UrlNoPad(mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)));

    return payload + "." + signature;
}

private static String base64UrlNoPad(byte[] src) {
    return Base64.getUrlEncoder().withoutPadding().encodeToString(src);
}

5.3 Server tokenni qayerda tekshiradi (konseptual kod)

public InvitePayload verify(String token) {
    String[] parts = token.split("\\.");
    if (parts.length != 2) {
        throw new IllegalArgumentException("malformed token");
    }
    String payload = parts[0];
    String given = parts[1];
    String expected = sign(payload); // 발급과 동일한 HMAC

    if (!MessageDigest.isEqual(
            given.getBytes(StandardCharsets.US_ASCII),
            expected.getBytes(StandardCharsets.US_ASCII))) {
        throw new IllegalArgumentException("bad signature");
    }

    InvitePayload body = decode(payload);
    if (Instant.now().isAfter(body.expiresAt())) {
        throw new IllegalArgumentException("expired");
    }
    return body;
}

String == taqqoslash o‘rniga MessageDigest.isEqual’dan foydalanishning sababi — uzunlik yoki mazmun farq qilgan taqdirda ham taqqoslash vaqtining bir tomonga og‘ib ketishining oldini olish amaliyotidir. Taklif havolalari uchun amaliy jihatdan sezilarli farqni payqamagan bo‘lsak-da, imzolarni taqqoslash uchun bu mos tanlovdir. Imzo muvaffaqiyatli tekshirilgandan keyin amal qilish muddatini tekshiramiz. Agar imzoni tekshirmasdan faqat amal qilish muddatini tekshirsak, amal qilish muddati o‘zgartirilgan tokenni qabul qilib qo‘yishimiz mumkin.

5.4 Frontend nima qiladi

Frontend kalitni bilmaydi. Jamoa rahbari ekranida jamoa rahbari “Havola yaratish” tugmasini bosganda, server tokenni taqdim etadi, frontend esa uni faqat query parametri sifatida qo‘shadi. Xodim deep link orqali kirganda, aynan shu qator registration API body’iga qayta joylashtiriladi.

// 반장: 서버가 준 토큰만 붙인다
const shareUrl = `${origin}/job/jobs/pool-invitations?token=${encodeURIComponent(token)}`;

// 일꾼: 받은 토큰 문자열을 그대로 등록에 낸다
await JobWkrFlowApi.registerContactPoolInvitation({ token });

Biz sheet har safar ochilganda yangi token yaratish haqidagi mahsulot qaroriga amal qildik. Bir nechta xodim bir xil tokendan foydalanishi mumkinligi sababli, bu bir martalik token emas. “Yangi token” mohiyatan yangi amal qilish muddatini belgilashni anglatadi va avvalgi tokenning muddati tugamagan bo‘lsa, ikkala token ham amal qilishi mumkin. Bu nuqtani mahsulot jamoasi bilan yana bir bor tasdiqlash maqsadga muvofiq.

5.5 Deployment vaqtida kalit amalda qanday yetkaziladi

Funksiya development va staging’da yml’dagi standart qiymat yordamida ishlaydi. Production’da qiymatni BANJANG_CONTACT_POOL_INVITE_HMAC_SECRET kaliti ostidagi Secret’ga joylaymiz va Deployment’ning env yoki envFrom’i orqali xuddi shu nomni pod’ga taqdim etamiz. Maxfiy kalitni ConfigMap’ga joylamaymiz. ConfigMap’lar ko‘pincha oddiy matn sifatida boshqariladi.

# 운영 Secret 값 만들기 (실값은 기록하지 않음)
openssl rand -base64 32

# 개념적인 매핑. 키 이름은 yml placeholder와 동일해야 한다
apiVersion: v1
kind: Secret
metadata:
  name: banjang-contact-pool-invite
type: Opaque
stringData:
  BANJANG_CONTACT_POOL_INVITE_HMAC_SECRET: "<openssl 결과>"

Faqat ushbu funksiyani hisobga olganda, avval staging uchun ConfigMap yaratish so‘rovi erta berilgan edi. Production deployment’idan oldin standart qiymatni Secret yordamida almashtirish vazifasini alohida bajarishimiz mumkin. Agar development, staging va production bir xil qiymatdan foydalansa, development’da sizib chiqqan kalit production havolalarini qalbakilashtirish uchun ishlatilishi mumkin.

6. Amalga oshirish vaqtida tiqilib qolgan nuqtalarimiz

6.1 Imzolash uchun qaysi target qator ishlatilishi kerak

JSON’ni avval HMAC qilish va keyin Base64 qo‘llash kerakmi yoki Base64 payload qatorini HMAC qilish kerakmi, degan masalada qisqa vaqtga tiqilib qoldik. Token beruvchi va tekshiruvchi tomonlar bir xil yondashuvdan foydalanishi kifoya. Imzolash target’i sifatida URL’ga joylashtirilgan payload qatorini tanladik. Dekodlagandan keyin JSON baytlarini qayta imzolasak, JSON kalitlari tartibi yoki bo‘sh joylar farq qilganda muammo yuzaga kelishi mumkin.

6. Standart Base64’dan bevosita foydalanish havolalarni buzadi

Hamkasbimizning snippet’ida standart Base64 ishlatilgan edi. Query ichiga joylashtirilganda, ayrim muhitlarda + belgisi bo‘sh joy sifatida talqin qilinadi. = padding belgisi ham noqulay. Padding’siz URL-safe kodlashga standartlashganimizdan keyingina token messaging ilovalari yoki matnli xabarlar orqali ulashilganda o‘z holatini saqlab qoldi.

6.3 Amal qilish muddatini faqat ro‘yxat filtri sifatida boshqarishni chetlab o‘tish mumkin

Muddati o‘tgan takliflarni ro‘yxatdan yashirish va registration API’ning muddati o‘tgan tokenlarni rad etishi — ikki xil narsadir. Agar faqat birinchisini bajarsak, eski havolani bookmark qilgan kishi API’ni bevosita chaqirishi mumkin. Haqiqiy talab — tekshiruv vaqtida Instant.now() qiymatini payload’dagi amal qilish muddati bilan taqqoslashdir.

6.4 Frontendda qayta imzolash vasvasasi

Agar amal qilish muddatini ekranda ko‘rsatmoqchi bo‘lsangiz, frontend payloadni dekodlashi mumkin. Uni dekodlash va imzolash ikki xil narsadir. Payload ko‘rinadigan bo‘lishi uchun mo‘ljallangan, uni o‘zgartirish va qayta imzolash uchun ishlatiladigan kalit esa frontendda saqlanmasligi kerak. Agar amal qilish muddati kerak bo‘lsa, server berish javobiga expiresAt ni qo‘shishi mumkin.

7. Natijada nimalar o‘zgardi

  • Endi taklif havolalarida amal qilish muddati mavjud va muddat tugaganidan keyingi ro‘yxatdan o‘tishlar server tomonidan rad etiladi.

  • Faqat pool IDsi o‘zgartirilgan URL o‘tmaydi, chunki uning imzosi yaroqsiz.

  • Frontend repositorysida maxfiy kalit yo‘q. Faqat token satri uzatiladi.

  • Operatsion kalit muhit o‘zgaruvchisi nomini Secret kaliti bilan moslashtirish vazifasidan ajratildi.

Ushbu maqolada nechta hujum kamaygani miqdoriy jihatdan bayon qilinmagan. Bu ishning natijasi shundan iboratki, endi server amal qilish muddati va qalbakilashtirilganligini aniqlay oladigan havolalarga egamiz. Oldingi havola formatini vaqtincha qo‘llab-quvvatlash yoki qo‘llab-quvvatlamaslik deployment strategiyasi masalasi edi, shuning uchun qancha eski havolani qabul qilish alohida hal qilindi.

8. Bu ishni bajarganimdan keyin nimalarni o‘rgandim

HMACdan foydalanadigan kodni qayerga joylashtirish va kalitni qayerda saqlash — ikki xil masala. Ilova algoritmdan allaqachon foydalanadi, operatsiyalarda esa podning muhit o‘zgaruvchisi o‘zgaradi. Bir vaqtning o‘zida ko‘proq kod yozish va klasterdagi nomlarni moslashtirish haqida o‘ylasangiz, Secret yaratishning o‘zi kifoya deb yanglish o‘ylash oson.

Frontend imzo hisoblagich emas. U faqat token satrini uzatadi. Shuningdek, bitta kalit bitta havolani anglatmaydi. Har bir muhit uchun bitta kalit mavjud, payload esa har bir havolada farq qiladi. Faqat shu xususiyat uchun avval stage ConfigMap so‘rash shart emas edi, productionda standart qiymatni qayta belgilashning esa alohida sababi bor edi.

Umumiy Util yaratishdan oldin HMAC domen servisida allaqachon mavjud yoki yo‘qligini tekshirish odatini shakllantirdim. Hatto faqat kodlash usuli farq qilganida ham, buning sababi ko‘pincha utilita yo‘qligi emas, balki amalda ishlatilayotgan alifbodan boshqa alifbodan foydalanish niyati bo‘ladi.

9. Amaliy tajribamdan nimalarni qoldirmoqchiman

  • Imzolangan tokenlardan foydalanganda ham maxfiy kalitni klientga joylashtirmang.

  • Operatsion Secret yaratishda yml tomonidan murojaat qilinadigan muhit o‘zgaruvchisi nomidan aynan foydalaning. Uni development va stage uchun berilgan standart qiymatlardan farqli qiymatga o‘rnating.

  • Har bir havola uchun alohida kalit yaratish haqida o‘ylasangiz, operatsiyalarni bajarib bo‘lmaydi. Umumiy kalit va turli payloadlar kifoya qiladi.

  • URLlarda HMAC qiymatlaridan foydalanilganda +, / va = havolalarni buzishi mumkin, shuning uchun avval kodlash URL uchun xavfsiz va padding ishlatilmaganligini tasdiqlang.

  • Amal qilish muddatini ro‘yxat filtrida emas, tekshirish vaqtida qo‘llang.

  • Haqiqiy kalit qiymatlarini o‘quv qaydlariga yoki wiki’ga biriktirmang. Faqat ularni yaratishda ishlatilgan buyruqni qoldiring.

Masalan, kalit yaratish openssl rand -base64 32 buyrug‘i natijasini Secret qiymatiga joylashtirishni anglatadi. Men biror kishi uzun jumlani qo‘lda yozadigan usuldan foydalanmadim.

10. Ko‘rib chiqishga arziydigan bog‘liq tushunchalar

  • URL-safe Base64: Satrni havolalar yoki so‘rovlarga joylashtirilganda ham o‘zgarmagan holda saqlaydigan kodlash usuli

  • Environment Variables va Kubernetes Secret / ConfigMap: Maxfiy ma’lumotlar uchun Secret, oddiy matnli konfiguratsiya uchun esa ConfigMap

  • Token Expiration: Tekshirish vaqtida rad etish va ro‘yxat ekranida elementni yashirish — ikki xil narsadir

  • Shared Links va Signed Invitation Links: Manzilni istalgan kishi ochishi mumkinmi yoki u qalbakilashtirishning oldini oladimi

11. Xulosa

Taklif havolasiga amal qilish muddatini qo‘shish dastlab ekran matnini o‘zgartirishdek ko‘rindi. Buni amalda bajarganimdan so‘ng “Uni kim imzolaydi?”, “Kalit qayerda?” va “Har bir havolaning kaliti boshqachami?” kabi savollar bir vaqtning o‘zida paydo bo‘ldi. HMACning o‘zidan ko‘ra, maxfiy ma’lumotlarni klientdan uzoqda saqlash va muhit o‘zgaruvchilari nomlarini birorta belgini ham xato qilmasdan moslashtirish jarayoni yodimda qoldi.

Agar ayni talab yana paydo bo‘lsa, avval algoritmni tanlamayman. Dastlab havola umumiy ulashiladimi, uning amal qilish muddatini kim belgilaydi va har bir muhit uchun nechta kalit bo‘lishi kerakligini yozib olaman, keyin HMAC, JWT yoki boshqa biror narsani tanlayman. Ushbu maqoladagi kod repositoryga yuklanmagan, shuning uchun u konseptual xususiyatga ega. Jamoa kodidagi klass nomlari va konfiguratsiya kalitlari deployment qilingan branch bilan yana bir bor tekshirilishi kerak.

Mina

Site footer