Опирается на правила:
AUTH-13…AUTH-14из Auth Patterns Style Guide → раздел 5. Service-to-service.
Важно знать
- Два способа межсервисной аутентификации: mTLS (рекомендуется) или Client Credentials Flow.
- mTLS — двусторонний TLS на K8s Service Mesh (Istio, Linkerd). Identity = CN client-сертификата.
- Client Credentials Flow —
grant_type=client_credentials, сервис получает access_token от IdP сscope=service:operation.- Анонимный inter-service трафик — критическое нарушение. Любой
RestClientбез mTLS илиBearer— на ревью.- mTLS даёт автоматическую identity-привязку — не нужно вручную проставлять headers.
- Один кросс-сервисный токен с broad scope — антипаттерн (нет blast-radius containment).
- Hard-coded shared secret в коде — никогда. Только env / Vault.
Service-to-service вызовы внутри кластера часто кажутся «безопасными по определению» — VPC isolated, internal network. Это иллюзия: insider attack, compromised pod, lateral movement — реальные сценарии. UCP формулирует правило «zero trust»: каждый межсервисный вызов аутентифицирован.
Способ 1: mTLS (рекомендуется)
AUTH-13: двусторонний TLS через Service Mesh.
order-service pod → istio-proxy sidecar → mTLS encrypted → istio-proxy → payment-service pod
(cert: order-service-prod) (verifies cert)
Istio автоматически:
- Раздаёт каждому pod-у уникальный client-сертификат (через SPIFFE identity).
- Шифрует все internal-вызовы TLS 1.2+.
- Verify-ит client cert на receiver-side.
В Spring приложении ничего custom не нужно — sidecar обрабатывает mTLS до Spring. Receiver получает identity через header (X-Forwarded-Client-Cert или Istio-specific) или через mTLS-аутентификацию на стороне Spring Security, если sidecar её прокидывает.
@Configuration
@ConditionalOnProperty(name = "security.mtls.enabled", havingValue = "true")
public class MtlsSecurityConfig {
@Bean
SecurityFilterChain internalApi(HttpSecurity http) throws Exception {
return http
.securityMatcher("/internal/**")
.x509(x509 -> x509
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(serviceUserDetails()))
.authorizeHttpRequests(authz -> authz.anyRequest().authenticated())
.build();
}
@Bean
UserDetailsService serviceUserDetails() {
return username -> User.builder()
.username(username)
.password("")
.authorities("ROLE_system")
.build();
}
}
Преимущества mTLS:
- Identity встроена в transport — нечего «забыть» добавить в headers.
- Rotation автоматическая — Istio обновляет сертификаты каждые 24 часа.
- Cross-language — работает одинаково для Java, Go, Python сервисов.
Недостатки:
- Требует Service Mesh инфры (Istio/Linkerd) — это серьёзная инфра-нагрузка.
- В dev/test без Service Mesh — нужны альтернативы.
Способ 2: Client Credentials Flow
AUTH-13: OAuth2 standard.
order-service → IdP: POST /oauth/token
grant_type=client_credentials
client_id=order-service-prod
client_secret=$ORDER_SERVICE_CLIENT_SECRET
scope=payment:charge
← IdP: { "access_token": "...", "expires_in": 3600 }
order-service → payment-service:
POST /charge
Authorization: Bearer <token>
payment-service: oauth2ResourceServer.jwt() валидирует токен,
видит `scope=payment:charge`, разрешает.
spring:
security:
oauth2:
client:
registration:
payment-service:
client-id: order-service-prod
client-secret: ${ORDER_SERVICE_CLIENT_SECRET}
authorization-grant-type: client_credentials
scope: payment:charge
provider:
payment-service:
token-uri: ${IDP_TOKEN_URI}
@Configuration
@RequiredArgsConstructor
public class PaymentClientConfig {
@Bean
OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository registrations,
OAuth2AuthorizedClientService clients
) {
var provider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
var manager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(registrations, clients);
manager.setAuthorizedClientProvider(provider);
return manager;
}
@Bean
RestClient paymentRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
var interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
interceptor.setClientRegistrationIdResolver(req -> "payment-service");
return RestClient.builder()
.baseUrl("https://payment-service.internal")
.requestInterceptor(interceptor)
.build();
}
}
Spring Security автоматически:
- Запрашивает token у IdP при первом вызове.
- Кеширует token до
exp - 30s. - Добавляет
Authorization: Bearer <token>к каждому requests. - Refresh-ит при expiry.
В UCP-сервисах именованные scope per operation (payment:charge, payment:refund, inventory:reserve), не general service. Это даёт fine-grained ACL: order-service может charge, но не может refund.
Запрет анонимного трафика
AUTH-14: RestTemplate.exchange(...) без auth — критическое нарушение.
// КАТАСТРОФА
@Component
public class PaymentClient {
private final RestTemplate restTemplate;
public Receipt charge(Long orderId, Money amount) {
return restTemplate.postForObject(
"http://payment-service/charge",
new ChargeRequest(orderId, amount),
Receipt.class
);
}
}
Любой компонент в сети может вызывать payment-service. Один скомпрометированный pod (например, через image vulnerability) → доступ ко всему cluster.
Корректно: либо mTLS через sidecar (нет custom-кода для auth), либо OAuth2 Client (RestClient с OAuth2ClientHttpRequestInterceptor).
ArchUnit-правило для проверки:
@ArchTest
public static final ArchRule outAdapterUsesAuthorizedClient =
classes()
.that().resideInAPackage("..adapter.out..")
.and().haveSimpleNameEndingWith("Client")
.should().dependOnClassesThat().haveSimpleName("OAuth2AuthorizedClientManager")
.orShould().dependOnClassesThat().haveSimpleName("OAuth2ClientHttpRequestInterceptor");
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
RestTemplate.exchange без Authorization | AUTH-14 | mTLS или OAuth2 Client |
| Анонимный inter-service трафик | AUTH-14 | zero trust |
| Hard-coded shared secret для S2S | AUTH-17 | env / Vault |
| Один токен на все S2S операции | AUTH-13 | scope per operation |
client_secret в application.yml | AUTH-17 | env через ${ORDER_SERVICE_CLIENT_SECRET} |
| Manual token caching | AUTH-13 | OAuth2AuthorizedClientManager делает сам |
| mTLS только для public-facing | AUTH-13 | zero trust в internal тоже |
| RestClient без interceptor — auth добавлен в Handler | AUTH-14 | interceptor централизованно |
Куда дальше
- Auth → раздел 5. Service-to-service — нормативные формулировки.
- JWT validation — payment-service валидирует токен от order-service.
- PII и секреты —
client_secretчерез Vault. - Resilience → integration —
RestClientper-external-system. - Kafka → security — TLS + ACL для Kafka cross-service.
- Где какая проверка —
systemrole для S2S.