Реализация аутентификации и авторизации на уровне API Gateway
Вынос аутентификации и авторизации в API Gateway позволяет разгрузить микросервисы от повторяющейся логики проверки токенов. Сервисы получают только проверенные запросы с уже извлечёнными claims.
Схема работы
Client → [API Gateway: validate JWT, extract claims] → Service
↑
X-User-ID, X-Role, X-Tenant headers
Сервисы доверяют заголовкам от Gateway и не делают собственную JWT-валидацию. Внешний доступ к сервисам напрямую — закрыт через сетевые политики.
JWT валидация в Kong
# Включить JWT плагин
curl -X POST http://localhost:8001/plugins \
-d "name=jwt"
# Создать consumer и credentials
curl -X POST http://localhost:8001/consumers \
-d "username=frontend-app"
curl -X POST http://localhost:8001/consumers/frontend-app/jwt \
-d "algorithm=RS256" \
-d "rsa_public_key=$(cat /keys/public.pem)"
Клиент передаёт: Authorization: Bearer <JWT>. Kong проверяет подпись и срок действия, при ошибке возвращает 401.
OAuth 2.0 через Kong
curl -X POST http://localhost:8001/services/api/plugins \
-d "name=oauth2" \
-d "config.scopes[]=read" \
-d "config.scopes[]=write" \
-d "config.mandatory_scope=true" \
-d "config.token_expiration=3600" \
-d "config.enable_client_credentials=true" \
-d "config.enable_authorization_code=true"
OIDC в APISIX
{
"plugins": {
"openid-connect": {
"client_id": "api-gateway",
"client_secret": "secret",
"discovery": "https://keycloak.company.com/realms/myapp/.well-known/openid-configuration",
"scope": "openid profile",
"set_access_token_header": true,
"set_userinfo_header": true,
"token_signing_alg_values_expected": ["RS256"],
"introspection_endpoint_auth_method": "client_secret_post",
"set_id_token_header": false
}
}
}
Кастомный Lambda Authorizer (AWS API Gateway)
# authorizer.py
import json
import jwt
import boto3
def handler(event, context):
token = event['authorizationToken'].replace('Bearer ', '')
try:
# Получить публичный ключ из AWS Secrets Manager
secret = boto3.client('secretsmanager').get_secret_value(
SecretId='jwt-public-key'
)
public_key = secret['SecretString']
payload = jwt.decode(
token,
public_key,
algorithms=['RS256'],
audience='api.company.com'
)
# Проверка дополнительных прав
if not check_permissions(payload, event['methodArn']):
return generate_policy(payload['sub'], 'Deny', event['methodArn'])
return generate_policy(
payload['sub'],
'Allow',
event['methodArn'],
context={
'user_id': payload['sub'],
'tenant_id': payload.get('tenant_id'),
'role': payload.get('role', 'user')
}
)
except jwt.ExpiredSignatureError:
raise Exception('Token expired')
except jwt.InvalidTokenError:
raise Exception('Unauthorized')
def generate_policy(principal, effect, resource, context=None):
policy = {
'principalId': principal,
'policyDocument': {
'Version': '2012-10-17',
'Statement': [{
'Action': 'execute-api:Invoke',
'Effect': effect,
'Resource': resource
}]
}
}
if context:
policy['context'] = context
return policy
def check_permissions(payload, method_arn):
role = payload.get('role', 'user')
# Извлечь HTTP метод из ARN
parts = method_arn.split(':')[-1].split('/')
http_method = parts[2] if len(parts) > 2 else 'GET'
if http_method in ['POST', 'PUT', 'DELETE'] and role == 'readonly':
return False
return True
Передача claims в upstream-сервисы
После валидации Gateway добавляет заголовки:
# Traefik ForwardAuth response headers propagation
http:
middlewares:
jwt-auth:
forwardAuth:
address: "http://auth-service:4000/validate"
authResponseHeaders:
- X-User-ID
- X-User-Role
- X-Tenant-ID
- X-Subscription-Plan
-- Kong plugin: извлечение claims из JWT и добавление в upstream headers
local jwt_decoder = require "kong.plugins.jwt.jwt_parser"
local function execute(conf)
local token = kong.request.get_header("authorization")
if token then
token = token:gsub("Bearer ", "")
local jwt_obj = jwt_decoder:new(token)
local claims = jwt_obj.claims
kong.service.request.set_header("X-User-ID", claims.sub)
kong.service.request.set_header("X-Tenant-ID", claims.tenant_id)
kong.service.request.set_header("X-User-Role", claims.role)
end
end
RBAC (Role-Based Access Control) на уровне Gateway
# Kong: разные consumer groups с разными правами
# Группа "admins" — полный доступ
# Группа "readonly" — только GET запросы
# ACL плагин
curl -X POST http://localhost:8001/services/admin-api/plugins \
-d "name=acl" \
-d "config.allow[]=admin-group" \
-d "config.hide_groups_header=true"
# Назначить consumer в группу
curl -X POST http://localhost:8001/consumers/alice/acl \
-d "group=admin-group"
Mutual TLS (mTLS) для service-to-service
# Kong: требовать клиентский сертификат
curl -X POST http://localhost:8001/services/internal-api/plugins \
-d "name=mtls-auth" \
-d "config.ca_certificates[]=$(cat ca-cert.pem)" \
-d "config.skip_consumer_lookup=true"
Обработка refresh token
// Middleware перед Gateway: автоматическое обновление токена
async function refreshMiddleware(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '')
if (isExpiringSoon(token)) {
const newToken = await refreshAccessToken(req.cookies.refresh_token)
res.setHeader('X-New-Token', newToken)
req.headers.authorization = `Bearer ${newToken}`
}
next()
}
Срок выполнения
Настройка JWT/OIDC аутентификации с RBAC и передачей claims в сервисы — 2–3 рабочих дня. С кастомным Lambda Authorizer и mTLS — 4–5 дней.







