🏗️ Розділ 17 · Питання #23

Як реалізувати автентифікацію та авторизацію в мікросервісах

У розподіленій системі задачі безпеки поділяються на два базових поняття:


🟢 Junior Level

У розподіленій системі задачі безпеки поділяються на два базових поняття:

  • Автентифікація (Authentication / AuthN): Перевірка справжності — «Хто ти такий?» (логін, пароль, біометрія, одноразовий код, SSO).
  • Авторизація (Authorization / AuthZ): Перевірка прав доступу — «Що тобі дозволено робити?» (ролі ROLE_ADMIN, дозвіл на читання конкретного документа).
                      ┌───────────────────────────────────────────────┐
                      │    Централізований Identity Provider (IdP)    │
                      │               (Keycloak / Okta)               │
                      └───────────────────────▲───────────────────────┘
                                              │ 1. Login / OAuth2 PKCE
                                              ▼ 2. JWT Access Token
[SPA / Mobile Client] ──3. Request + Bearer JWT──► [API Gateway]
                                                         │ 4. Валідація підпису (JWKS)
                                                         ▼ 5. Збагачення: X-User-Id: 42
                                                   [Order Service]

Головний стандарт: OAuth 2.0 + OpenID Connect (OIDC) + JWT

  1. Користувач автентифікується один раз у централізованому сервісі ідентифікації (Identity Provider / Keycloak).
  2. Клієнт отримує JWT (JSON Web Token), що містить sub (ID користувача), roles (ролі) та цифровий криптографічний підпис Signature.
  3. Усі наступні запити супроводжуються заголовком: Authorization: Bearer <JWT-токен>
  4. API Gateway перевіряє валідність токена на зовнішньому периметрі та перенаправляє запит у внутрішню мережу.

🟡 Middle Level

1. Архітектурні патерни передачі контексту користувача

Патерн Як влаштований Переваги Недоліки / Ризики
Edge Authentication + Headers Gateway валідує JWT, вилучає claims і прокидає внутрішні HTTP-заголовки X-User-Id, X-User-Roles. Внутрішні сервіси прості, не потребують криптографічних бібліотек. 🔴 Вразливе до підробки заголовків усередині контуру (порушення принципу Zero Trust).
Token Relay Gateway прокидає клієнтський JWT без змін крізь увесь ланцюжок мікросервісів. Висока безпека: кожен сервіс бачить оригінального ініціатора операції. Підвищений CPU-overhead на повторну перевірку криптографічного підпису JWT у кожному сервісі.
Phantom Token Pattern Клієнт отримує непрозорий Reference Token (UUID). Gateway замінює його на повноцінний криптографічний JWT для внутрішніх сервісів. Клієнт не бачить внутрішньої структури JWT; максимальна безпека. Додатковий мережевий пошук у кеші Redis на рівні шлюзу.

2. Реалізація Resource Server у Spring Security 6 (Spring Boot 3)

Внутрішній мікросервіс налаштовується як OAuth2 Resource Server, що перевіряє JWT через публічні ключі Identity Provider (JWKS):

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          # Адреса, звідки сервіс автоматично завантажує публічні ключі для перевірки підпису
          jwk-set-uri: http://keycloak:8080/realms/production/protocol/openid-connect/certs
          issuer-uri: http://keycloak:8080/realms/production

Конфігурація авторизації та рольового доступу в Java:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity // Вмикає підтримку @PreAuthorize
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/public/**").permitAll()
                .requestMatchers(HttpMethod.POST, "/api/v1/orders/*").hasAuthority("SCOPE_orders.write")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
            .build();
    }
}

// Захист конкретного методу бізнес-логіки:
@Service
public class OrderService {

    @PreAuthorize("hasRole('ADMIN') or #userId == authentication.name")
    public OrderDto getOrderForUser(Long orderId, String userId) {
        // Доступ дозволено лише адміністратору або власнику замовлення
        return orderRepository.findOrder(orderId);
    }
}

🔴 Senior Level

1. Безпека міжсервісної взаємодії: Zero Trust & mTLS

У концепції Zero Trust (Нульова довіра) внутрішній мережевий периметр вважається спочатку ворожим (скомпрометованим). Якщо зловмисник зламав один под, він не повинен мати змоги надсилати фальшиві заголовки X-User-Id: admin на сусідні сервіси.

[Service A (Order)] ──(1. mTLS + SPIFFE ID)──► [Service B (Billing)]
        │                                             │
        └── Взаємна автентифікація сертифікатами x509 ┘
            (Шифрування трафіку + валідація SAN: spiffe://cluster/ns/prod/sa/order-sa)
  1. Service-to-Service AuthN: Реалізується через mTLS (Mutual TLS) на рівні Service Mesh (Istio / Envoy). Сторони перевіряють x509-сертифікати одна одної, гарантуючи, що викликач — це дійсно авторизований order-service, а не сторонній підроблений вузол.
  2. Service-to-Service AuthZ: Застосування протоколу OAuth 2.0 Client Credentials Flow: сервіс запитує в Keycloak технічний сервісний токен для фонових інтеграційних завдань.

2. Проблема негайного відкликання JWT (Revocation Problem)

JWT є stateless-токеном: якщо його випущено з терміном дії 24 години, відкликати його неможливо без звернення до спільної бази даних, поки не закінчиться термін exp (Expiration). Якщо акаунт користувача скомпрометовано або співробітника звільнено, зловмисник може безперешкодно діяти годинами.

Архітектурні рішення:

  1. Short-Lived Access Tokens (Короткоживучі токени):
    • Час життя Access Token встановлюється на $5\text{–}15$ хвилин.
    • Клієнт оновлює токен у фоні через Refresh Token, який зберігається в базі даних Identity Provider із механізмом одноразової ротації (Refresh Token Rotation). При компрометації Refresh Token блокується в IdP, і доступ припиняється протягом кількох хвилин.
  2. Distributed Blacklist / Bloom Filter у Redis: При виході із системи (Logout) jti (JWT ID) токена розміщується в Redis-кеші з TTL, рівним залишку часу до exp. Шлюз перевіряє наявність jti в Redis. Для оптимізації пам’яті застосовується фільтр Блума (Bloom Filter).

3. Проблема «Confused Deputy» та Token Exchange (RFC 8693)

Якщо Сервіс $A$ викликає Сервіс $B$ від імені користувача, а Сервіс $B$ викликає Сервіс $C$:

  • Якщо Сервіс $B$ просто передає далі оригінальний клієнтський JWT, недобросовісний компонент Сервісу $B$ може скористатися цим токеном для несанкціонованого виклику будь-яких інших сервісів компанії від чужого імені.
  • Рішення: OAuth 2.0 Token Exchange (RFC 8693). Сервіс $B$ обмінює вхідний токен користувача в IdP на новий похідний токен із суворо обмеженим списком отримувачів (aud: service-c) та урізаними скоупами (Downscoped Scopes).

4 Tricky Questions

1. Чому сліпа перевірка заголовка X-User-Id без перевірки підпису у внутрішньому контурі є критичною вразливістю?

Відповідь: Якщо мікросервіси всередині периметра сліпо довіряють заголовкам X-User-Id та X-User-Roles, припускаючи, що їх виставив API Gateway: Будь-який скомпрометований внутрішній под (наприклад, через вразливість сторонньої бібліотеки на кшталт Log4Shell чи через SSRF) може надіслати HTTP-запит безпосередньо в Payment Service із підробленим заголовком X-User-Roles: ROLE_SUPER_ADMIN та X-User-Id: 1. Захист:

  1. Gateway підписує внутрішній запит полегшеним внутрішнім асиметричним токеном.
  2. Мережеві політики Kubernetes (NetworkPolicy) та Service Mesh (AuthorizationPolicy) блокують будь-який прямий вхідний трафік до сервісів в обхід шлюзу.

2. Як мікросервіси перевіряють підпис JWT без мережевого звернення до Keycloak на кожен запит?

Відповідь: Використовується асиметричне шифрування (RS256 / ES256) та механізм JWKS (JSON Web Key Set):

  1. Keycloak підписує токен своїм приватним ключем, який зберігається в суворій таємниці.
  2. Мікросервіси під час старту один раз завантажують набір публічних ключів за адресою /.well-known/jwks.json і кешують їх у внутрішній пам’яті.
  3. Валідація підпису виконується локально в пам’яті процесора (in-memory) за долі мілісекунди без мережевих затримок.
  4. У заголовку JWT передається ідентифікатор ключа kid (Key ID). Якщо Keycloak проводить ротацію ключів, сервіс отримує невідомий kid, робить одноразовий запит за оновленим JWKS і освіжає локальний кеш.

3. У чому різниця між RBAC, ABAC та ReBAC при авторизації в мікросервісах?

Відповідь:

  • RBAC (Role-Based Access Control): Доступ визначається статичними ролями (ROLE_MANAGER, ROLE_ADMIN). Просто у реалізації, але негнучко: складно реалізувати правило «Менеджер може редагувати замовлення лише свого підрозділу».
  • ABAC (Attribute-Based Access Control): Доступ обчислюється динамічно на основі атрибутів суб’єкта, ресурсу та оточення (user.dept == order.dept && time >= 09:00). Реалізується через спеціалізовані рушії політик (Open Policy Agent / OPA).
  • ReBAC (Relationship-Based Access Control): Доступ визначається графом зв’язків між об’єктами (Google Zanzibar / Ory Keto): «Користувач може редагувати документ, якщо він є учасником групи, що володіє папкою, яка містить цей документ».

4. Де зберігати Refresh Token у вебзастосунках (SPA), щоб уникнути крадіжки через XSS?

Відповідь: Зберігання токенів у localStorage або sessionStorage браузера є вкрай небезпечним: будь-який успішний XSS-скрипт отримує прямий доступ до сховища браузера і миттєво краде токен. Безпечне рішення: HttpOnly, Secure, SameSite=Strict Cookie. Браузер автоматично передає таку cookie під час звернення до IdP/Gateway, проте клієнтський JavaScript (зокрема шкідливий XSS-код) фізично позбавлений доступу до її вмісту через document.cookie. Для нейтралізації атак CSRF застосовуються механізми Double Submit Cookie або суворий заголовок SameSite=Strict.


🎯 Шпаргалка для інтерв’ю

Головне:

  • Автентифікація — хто ти; Авторизація — що дозволено робити.
  • Галузевий стандарт: OAuth 2.0 + OpenID Connect (OIDC) із централізованим Identity Provider (Keycloak, Auth0).
  • Формат обміну даними користувача — JWT (JSON Web Token) з асиметричним підписом (RS256).
  • Мікросервіси перевіряють підпис локально in-memory за допомогою публічних ключів JWKS (без походів у базу даних на кожен виклик).
  • Short-Lived Access Tokens (5–15 хв) + Refresh Token Rotation вирішують проблему неможливості швидкого відкликання stateless JWT.
  • Принцип Zero Trust: захист міжсервісного трафіку через mTLS (Service Mesh) та перевірка прав на кожній ланці.
  • Контроль доступу: від RBAC в аннотаціях @PreAuthorize до ABAC / Open Policy Agent (OPA) для тонких динамічних політик.

Червоні прапорці (Чого говорити НЕ МОЖНА):

  • ❌ «JWT токени зашифровані, у них можна безпечно передавати паролі чи реквізити карток» (JWT підписаний, але НЕ зашифрований; Payload кодований у Base64Url і легко читається будь-ким).
  • ❌ «Кожен мікросервіс повинен ходити в Keycloak по HTTP на кожен запит для перевірки валідності токена» (Це створить гігантське вузьке місце; валідація підпису має відбуватися локально за публічним ключем).
  • ❌ «У внутрішньому контурі безпека не потрібна, за файрволом усі свої» (Грубе порушення принципу Zero Trust; скомпрометований вузол отримає тотальний доступ до системи).

Пов’язані теми: