Как реализовать аутентификацию и авторизацию в микросервисах
В распределённой системе задачи безопасности разделяются на два понятия:
🟢 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
- Пользователь логинится один раз в едином сервисе авторизации (Identity Provider / Keycloak).
- Клиент получает JWT (JSON Web Token), содержащий
sub(ID пользователя),roles(роли) и цифровую подписьSignature. - Все последующие запросы отправляются с заголовком:
Authorization: Bearer <JWT-токен> - 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; безопасность максимальна. | Дополнительный lookup в кэше 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)
- Service-to-Service AuthN: Реализуется через mTLS (Mutual TLS) на уровне Service Mesh (Istio / Envoy). Стороны проверяют x509-сертификаты друг друга, гарантируя, что вызывающий сервис — это действительно
order-service, а не подделка. - Service-to-Service AuthZ: Использование протокола OAuth 2.0 Client Credentials Flow: сервис запрашивает в Keycloak технический сервисный токен для выполнения системных фоновых задач.
2. Проблема немедленного отзыва JWT (Revocation Problem)
JWT является stateless-токеном: если токен выпущен со сроком действия 24 часа, отозвать его невозможно без обращения к центральной базе, пока не истечет поле exp (Expiration). Если аккаунт пользователя взломан или сотрудника уволили, злоумышленник может пользоваться украденным токеном часами.
Архитектурные решения проблемы:
- Short-Lived Access Tokens (Короткоживущие токены):
- Время жизни Access Token устанавливается в $5\text{–}15$ минут.
- Клиент обновляет токен в фоне через Refresh Token, который хранится в базе данных Identity Provider с механизмом ротации (Refresh Token Rotation). При компрометации Refresh Token блокируется в IdP, и через 5 минут доступ полностью прекращается.
- Distributed Blacklist / Bloom Filter в Redis:
При выходе из системы (Logout)
jti(JWT ID) токена помещается в Redis-кэш со сроком жизни, равным оставшемуся времени до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 без проверки подписи во внутреннем контуре является критической уязвимостью?
Ответ:
Если сервисы внутри периметра слепо доверяют HTTP-заголовкам X-User-Id и X-User-Roles, полагая, что их выставил API Gateway:
Любой внутренний сервис (например, под скомпрометированной сторонней библиотекой через уязвимость Log4Shell или SSRF) может отправить HTTP-запрос напрямую в Payment Service с заголовком X-User-Roles: ROLE_SUPER_ADMIN и X-User-Id: 1.
Защита:
- Gateway подписывает внутренний запрос облегченным внутренним асимметричным токеном.
- Сетевые политики Kubernetes (
NetworkPolicy) и Service Mesh (Istio AuthorizationPolicy) блокируют любой входящий трафик к сервисам в обход шлюза.
2. Как микросервисы проверяют подпись JWT без сетевого похода в Keycloak на каждый запрос?
Ответ: Используется асимметричное шифрование (RS256 / ES256) и механизм JWKS (JSON Web Key Set):
- Keycloak подписывает токен своим приватным ключом, который никому не отдает.
- Микросервисы при старте один раз загружают набор публичных ключей по URL
/.well-known/jwks.jsonи кэшируют их в оперативной памяти. - Проверка подписи выполняется математически внутри процессора (in-memory) за доли миллисекунды без сетевых обращений к IdP.
- В заголовке JWT передается параметр
kid(Key ID). Если IdP сменил ключ, сервис видит неизвестныйkid, делает разовый сетевой запрос за новым JWKS и обновляет локальный кэш.
3. В чем разница между RBAC, ABAC и ReBAC при авторизации в микросервисах?
Ответ:
- RBAC (Role-Based): Доступ определяется статическими ролями (
ROLE_MANAGER,ROLE_ADMIN). Легко внедрить, но негибко: сложно выразить правило «Менеджер может редактировать заказ только своего филиала». - ABAC (Attribute-Based): Доступ рассчитывается динамически по атрибутам субъекта, ресурса и окружения (например,
if user.dept == order.dept and time >= 09:00). Реализуется через движки политик (Open Policy Agent / OPA). - ReBAC (Relationship-Based): Доступ определяется графом отношений между объектами (Google Zanzibar / Ory Keto): «Пользователь может редактировать документ, если он является участником группы, которая владеет папкой, содержащей этот документ».
4. Где хранить Refresh Token в веб-приложениях (SPA), чтобы избежать кражи через XSS?
Ответ:
Хранение токенов в localStorage или sessionStorage браузера крайне уязвимо: любой XSS-скрипт имеет прямой доступ к JS-хранилищу и может украсть токен.
Безопасное решение: 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 решают проблему невозможности отзыва JWT.
- Принцип Zero Trust: защита межсервисных вызовов через mTLS (Service Mesh) и проверка авторизации на каждом шаге.
- Ролевой контроль: от простого RBAC в аннотациях
@PreAuthorizeдо ABAC / Open Policy Agent (OPA) для сложных политик.
Красные флаги (Чего говорить НЕЛЬЗЯ):
- ❌ «JWT токены зашифрованы, в них можно безопасно хранить пароли и номера кредиток» (JWT подписан, но НЕ зашифрован; Payload закодирован в Base64Url и легко читается любым человеком).
- ❌ «Каждый сервис должен ходить в Keycloak по HTTP на каждый запрос для проверки токена» (Это превратит IdP в гигантское узкое место; проверка подписи выполняется локально по публичному ключу).
- ❌ «Во внутреннем контуре безопасность не нужна, внутри периметра все свои» (Грубейшее нарушение принципа Zero Trust; скомпрометированный сервис получает неограниченный доступ ко всей системе).