
はじめに
マイクロサービスにおける認証と認可はモノリスよりも複雑です。各サービスは「誰が電話をかけてきたのか?」を知る必要があります。 「彼らにはこれを行う権利があるのか?」 — リクエストごとにデータベースをチェックする必要がありません。
解決策: 一元化されたアイデンティティ プロバイダー (IdP) + JWT トークン — API ゲートウェイで 1 回認証すると、トークンはすべてのサービスに渡されます。
1. 認証と認可
Authentication (AuthN): "Bạn là ai?"
→ Verify identity (username/password, API key, certificate)
→ Kết quả: "Đây là user John, email [email protected]"
Authorization (AuthZ): "Bạn có thể làm gì?"
→ Verify permissions (roles, scopes)
→ Kết quả: "John có role 'customer', có thể tạo và xem orders của mình"
2. OAuth2 — 認可フレームワーク
2.1 OAuth2 の役割
Resource Owner: User (người dùng cuối)
Client: Application yêu cầu access (web app, mobile app)
Authorization Server: Keycloak/Auth0 — cấp tokens
Resource Server: API/Service chứa protected resources
2.2 認可コード フロー (Web アプリ)
Browser Backend App Auth Server (Keycloak)
│ │ │
│── Click Login ───────────▶│ │
│ │── Redirect ──────────────▶│
│◀─── Redirect to Login ────────────────────────────────│
│ │ │
│── Enter credentials ─────────────────────────────────▶│
│ │ │
│◀─── Redirect with code ───────────────────────────────│
│ │ │
│── Forward code ──────────▶│ │
│ │── Exchange code for token▶│
│ │◀─ access_token + id_token─│
│ │ │
│◀─── Session created ──────│ │
2.3 クライアント資格情報フロー (サービス間)
Service A Auth Server
│ │
│── POST /token │
│ client_id + client_secret ─────▶│
│◀─ access_token (JWT) ─────────────│
│ │
│── API Call + Bearer token ───────▶ Service B
│
│── Validate JWT (signature + expiry)
│── Check scopes
│
▼
Process request
2.4 PKCE フロー (モバイル/SPA)
クライアント シークレットなし (ユーザーのデバイスで実行されているコードを秘密にすることはできません):
# 1. Tạo code_verifier (random string)
code_verifier = base64url(random_bytes(32))
# 2. Tạo code_challenge
code_challenge = base64url(sha256(code_verifier))
# 3. Authorization request kèm code_challenge
GET /authorize?
response_type=code&
client_id=xxx&
code_challenge=abc&
code_challenge_method=S256
# 4. Token exchange kèm code_verifier
POST /token
code=xxx&code_verifier=original_verifier
3. JWT — JSON Web トークン
3.1 構造
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyLTAwMSIsInJvbGVzIjpbImN1c3RvbWVyIl19.signature
└─────────────────┘ └──────────────────────────────────────────┘ └───────────┘
Header Payload Signature
Header (Base64URL decoded):
{
"alg": "RS256", ← Thuật toán ký: RS256 (asymmetric) hoặc HS256 (symmetric)
"typ": "JWT",
"kid": "key-2024" ← Key ID để lookup public key
}
Payload (Base64URL decoded):
{
"sub": "user-001", ← Subject (user ID)
"iss": "https://auth.example.com", ← Issuer
"aud": "order-service", ← Audience (intended recipient)
"exp": 1711900800, ← Expiration time (Unix timestamp)
"iat": 1711897200, ← Issued At
"jti": "abc-123-xyz", ← JWT ID (prevent replay)
"email": "[email protected]",
"roles": ["customer"],
"tenant_id": "tenant-001" ← Custom claims
}
Signature: RS256(base64url(header) + "." + base64url(payload), private_key)
3.2 RS256 と HS256
HS256 (Symmetric):
- Ký và verify bằng cùng một secret key
- Tất cả services cần biết secret → nguy hiểm
- Dùng cho: monolith, internal tools
RS256 (Asymmetric):
- Auth Server ký bằng private key (giữ bí mật)
- Services verify bằng public key (có thể public)
- Services KHÔNG thể forge tokens
- Dùng cho: microservices, distributed systems
JWK Set (JWKS): Auth Server expose public keys tại /.well-known/jwks.json
Services cache và dùng để verify JWT offline
3.3 JWT の検証
@Component
public class JwtValidator {
// Cache JWKS (public keys) từ Auth Server
@Autowired
private JwkProvider jwkProvider;
public Claims validate(String token) {
// Parse header để lấy kid
DecodedJWT jwt = JWT.decode(token);
JwkKey key = jwkProvider.get(jwt.getKeyId());
try {
// Verify signature + claims
return JWT.require(Algorithm.RSA256(key.getPublicKey(), null))
.withIssuer("https://auth.example.com")
.withAudience("order-service")
.build()
.verify(token)
.getClaims();
} catch (TokenExpiredException e) {
throw new UnauthorizedException("Token expired");
} catch (JWTVerificationException e) {
throw new UnauthorizedException("Invalid token");
}
}
}
4. OpenID Connect (OIDC)
OAuth2 は承認フレームワークです。 OIDC は OAuth2 の ID レイヤーです。
OAuth2 trả về: access_token (opaque, for authorization)
OIDC thêm: id_token (JWT, contains user identity info)
/userinfo endpoint
Ví dụ id_token:
{
"sub": "user-001",
"name": "John Doe",
"email": "[email protected]",
"email_verified": true,
"picture": "https://...",
"iss": "https://auth.example.com",
"aud": "my-app",
"exp": 1711900800
}
OIDC を使用する場合: ユーザー プロファイル情報が必要な場合 (ログイン、プロファイル ページ)。アクセストークンはAPIにアクセスするために使用されます。
5. Keycloak — セルフホスト型アイデンティティプロバイダー
5.1 Kubernetes へのインストール
helm repo add bitnami https://charts.bitnami.com/bitnami
helm upgrade --install keycloak bitnami/keycloak \
--namespace platform \
--set auth.adminPassword=<password> \
--set replicaCount=2 \
--set postgresql.enabled=true \
--set ingress.enabled=true \
--set ingress.hostname=auth.example.com
5.2 レルム構成
// Realm: myapp
// Client: order-service-api (Resource Server)
{
"clientId": "order-service-api",
"protocol": "openid-connect",
"bearerOnly": true, // Chỉ accept tokens, không issue
"defaultClientScopes": ["openid", "profile", "email"],
"protocolMappers": [
{
"name": "tenant-id-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"config": {
"user.attribute": "tenant_id",
"claim.name": "tenant_id",
"access.token.claim": "true"
}
}
]
}
6. マイクロサービスでのトークンの伝播
6.1 API ゲートウェイ — 集中認証
Client ──Bearer token──▶ API Gateway
│
│ 1. Validate JWT signature
│ 2. Check expiry
│ 3. Check audience
│
│ 4. Forward token (or user info headers) → downstream services
│
▼
Order Service
│
│ 5. Extract claims từ token/headers
│ 6. Apply RBAC logic
│
▼
Process request
6.2 Kong JWT プラグイン
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: jwt-auth
plugin: jwt
config:
key_claim_name: kid
claims_to_verify:
- exp
- nbf
anonymous: null
run_on_preflight: true
# Hoặc dùng OIDC plugin
plugin: oidc
config:
issuer: https://auth.example.com/realms/myapp
client_id: kong-gateway
client_secret: xxxx
scope: openid
bearer_only: yes
6.3 トークン転送とヘッダー挿入
オプション 1: 元の JWT を転送 (推奨)
# Kong: forward original Authorization header
# Downstream service tự validate
利点: ダウンストリーム サービスには完全なクレームがあり、署名を個別に検証できます。
オプション 2: ヘッダー インジェクション (シンプルですが安全性は低くなります)
# Kong inject parsed claims làm headers
X-Consumer-Username: john
X-Consumer-Groups: customer,admin
X-Tenant-ID: tenant-001
利点: ダウンストリーム サービスには JWT ライブラリが必要ありません 欠点: これらのヘッダーは、誰かが API ゲートウェイをバイパスすると簡単に偽造されてしまいます。
7. 認可パターン
7.1 RBAC — ロールベースのアクセス制御
// Spring Security
@PreAuthorize("hasRole('ADMIN') or (hasRole('CUSTOMER') and #customerId == authentication.name)")
public Order getOrder(String orderId, String customerId) { ... }
// Hoặc Method Security
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public void deleteOrder(String orderId) { ... }
# Kubernetes RBAC cho service-to-service (service accounts)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: order-service-role
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list"]
7.2 ABAC — 属性ベースのアクセス制御
// Sử dụng Spring Security SpEL expression
@PreAuthorize("""
hasRole('CUSTOMER') and
@orderService.isOwner(#orderId, authentication.name) and
T(java.time.LocalDate).now().isBefore(
@orderService.getOrder(#orderId).createdAt.plusDays(30)
)
""")
public void cancelOrder(String orderId) { ... }
7.3 OPA (オープン ポリシー エージェント) — コードとしてのポリシー
# order-policy.rego
package order
default allow = false
# Customer có thể xem order của mình
allow {
input.action == "GET"
input.user.roles[_] == "customer"
input.resource.owner_id == input.user.id
}
# Admin có thể xem tất cả
allow {
input.user.roles[_] == "admin"
input.action == "GET"
}
# Refund chỉ trong 30 ngày
allow {
input.action == "REFUND"
input.user.roles[_] == "customer"
input.resource.owner_id == input.user.id
time.diff(input.resource.created_at, time.now_ns()) < 2592000 # 30 ngày
}
// Gọi OPA để evaluate policy
@Service
public class AuthorizationService {
public boolean isAllowed(String action, User user, Resource resource) {
OpaRequest request = OpaRequest.builder()
.input(Map.of(
"action", action,
"user", Map.of("id", user.getId(), "roles", user.getRoles()),
"resource", Map.of("owner_id", resource.getOwnerId(), "created_at", resource.getCreatedAt())
))
.build();
OpaResponse response = opaClient.evaluate("order/allow", request);
return response.getResult();
}
}
8. トークンの更新とセキュリティに関する考慮事項
8.1 アクセストークンとリフレッシュトークン
Access Token:
- Short-lived: 5-15 phút
- Dùng để access APIs
- Stateless (validate offline với public key)
- Khi bị stolen: expire sớm → damage limited
Refresh Token:
- Long-lived: 7-30 ngày
- Dùng để lấy access token mới
- Stored lên Auth Server (có thể revoke)
- Khi bị stolen: phải revoke ngay
8.2 セキュリティのベストプラクティス
□ Access token TTL: 5-15 phút (không phải 1 ngày)
□ RS256 không phải HS256 cho distributed systems
□ Validate audience (aud) claim — token không dùng được cho service khác
□ Validate issuer (iss)
□ Store tokens trong httpOnly cookies (không localStorage)
□ Refresh token rotation: mỗi lần refresh → issue token mới + revoke cũ
□ Token không chứa sensitive data (credit card, password)
□ Rate limit authentication endpoints
□ Brute force protection (lockout sau N failed attempts)
概要
| コンセプト | 目的 |
|---|---|
| OAuth2 | 認可フレームワーク (代理アクセス) |
| OIDC | OAuth2 の ID 層 (ユーザーが誰であるか) |
| JWT (RS256) | ステートレストークン、DBなしのオフライン検証 |
| JWKS | トークンを検証するためのサービスの公開キー エンドポイント |
| APIゲートウェイ認証 | 集中認証、ダウンストリームは検証済みのクレームを受け取ります |
| RBAC | ロールベースのアクセス - シンプル、予測可能 |
| アバック | 属性ベース — 柔軟、コンテキスト認識 |
| オーパ | コードとしてのポリシー — 一元化された監査可能な認可 |
次の記事: シークレット管理とコンテナーのセキュリティ