| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- 토큰
- Device Authorization
- CKA 유형
- Authorization Code
- initContainers
- standard flow
- password
- Circuit Breaker
- Client
- Realm
- CKA 가격
- OIDC
- fsGroup
- Helm
- Network Policies
- 인증
- 쿠버네티스
- MSA
- direct access grants
- K8S
- service account roles
- Kubernetes
- keycloak
- oauth2.0
- Fallback
- client credentials
- Istio
- 인가
- RBAC
- Ingress
- Today
- Total
redcedar137 님의 블로그
Istio Security Resource와 Keycloak 통합 인증 구현 본문
Istio Security Resource와 Keycloak 통합 인증 구현
1. 배경 – 왜 Istio 보안 정책과 Keycloak을 연동했는가
마이크로서비스 아키텍처로 전환하거나 서비스 수가 늘어날수록, 인증(Authentication)과 인가(Authorization)는 가장 먼저 복잡해지는 영역이다.
외부 요청은 Ingress 레벨에서 일정 수준 통제하고 있었지만, 내부 서비스 간 통신은 상대적으로 느슨한 경우가 많았다.
그 결과, 외부 접근은 차단하고 있었지만 내부 통신까지 포함한 종단 간(end-to-end) 보안 모델로 보기는 어려운 상태였다.
이러한 배경 속에서 자연스럽게 다음과 같은 질문에 도달했다.
- 인증과 인가를 애플리케이션 코드 밖으로 분리할 수는 없을까?
- SSO 기반 토큰 하나로 서비스 전반의 접근 제어를 통합할 수는 없을까?
- 경로·메서드 단위 인가를 코드 수정 없이 적용할 수는 없을까?
Istio, Keycloak, oauth2-proxy 간단 소개
본 글에서 다루는 인증·인가 구조의 핵심 구성 요소는 Keycloak과 Istio이다.
각 기술에 대한 상세한 설명보다는, 이 아키텍처에서 맡은 역할을 중심으로 간단히 정리한다.
추가로, 데모의 편의성을 위해 사용된 oauth2-proxy 또한 간단하게 소개한다.
Keycloak
- CNCF 생태계에서 널리 사용되는 오픈소스 Identity Provider(IdP)
- OIDC(OpenID Connect) 기반 인증 및 SSO 제공
- 사용자, 그룹, 역할(Role)을 중앙에서 관리
- JWT 토큰 발급 시 iss, aud, groups 등 인가에 필요한 클레임 포함 가능
이 구조에서 Keycloak은 “사용자가 누구인지 증명하는 주체” 역할만을 담당한다.
Istio
- Kubernetes 환경에서 서비스 간 통신을 제어하는 Service Mesh
- 트래픽 관리뿐 아니라 강력한 보안(Security) 기능 제공
- IngressGateway, RequestAuthentication, AuthorizationPolicy,
PeerAuthentication 등 보안 리소스를 통해
인증·인가·암호화를 애플리케이션 외부에서 처리 가능
이 구조에서 Istio는 “어떤 요청을 어디까지 허용할 것인가를 결정하는 정책 실행자” 역할을 한다.
oauth2-proxy
- CNCF 생태계에서 널리 사용되는 인증 프록시(Authentication Proxy)
- OIDC/OAuth2 기반 로그인 흐름을 애플리케이션 앞단에서 처리
- 인증되지 않은 요청은 IdP(Keycloak)로 리다이렉트
- 인증 완료 후에는 세션을 유지하며 요청에 토큰/헤더를 전달
이 구조에서 oauth2-proxy는 “브라우저 기반 인증 흐름을 담당하는 진입 관문” 역할을 한다.
2. 목표 – 인증은 Keycloak, 인가는 Istio에서
이번 아키텍처의 목표는 인증과 인가의 책임을 명확히 분리하는 것이다.
사용자가 누구인지는 Keycloak이 판단하고, 그 사용자가 어떤 요청을 수행할 수 있는지는 Istio가 결정하도록 설계했다.
설계 목표
- Keycloak을 단일 인증 주체(IdP) 로 사용
- OIDC 기반 JWT 발급
- iss (issuer)
- groups (권한 판단용 클레임)
- Istio Security 리소스를 활용한 선언적 보안 정책 적용
Istio Security 적용 범위
- JWT 검증
- RequestAuthentication을 통해 토큰 서명 및 클레임 검증
- 접근 제어(Authorization)
- AuthorizationPolicy로
- 요청 경로(Path)
- JWT 클레임(groups)
기반 인가 정책 적용
- AuthorizationPolicy로
- 서비스 간 통신 보안
- PeerAuthentication을 활용해 내부 서비스 간 통신까지 암호화(mTLS) 적용
핵심 설계 원칙
애플리케이션은 토큰을 검증하지 않고, 요청 처리만 한다.
- JWT 검증 및 접근 제어는 모두 Istio 계층에서 수행
- 애플리케이션은 보안보다는 비즈니스 로직에만 집중
이 글에서는 위 목표를 바탕으로,
Keycloak에서 발급한 토큰이 Istio Security 정책을 거쳐
어떻게 일관된 인증·인가 흐름으로 동작하는지를 단계적으로 살펴본다.
3. 전체 아키텍처 및 인증·인가 요청 흐름
아래 아키텍처는 Keycloak에서 발급된 JWT 토큰이
Istio Security 정책을 거쳐 애플리케이션으로 전달되는 전체 흐름을 나타낸다.
Architecture Overview & Request Flow

인증·인가 요청 흐름 (End-to-End)
1. 브라우저 요청 (HTTPS)
- 사용자는 브라우저를 통해 서비스 도메인으로 요청을 전송
- 모든 요청은 HTTPS로 Istio IngressGateway에 전달됨
2. IngressGateway → VirtualService 라우팅
- IngressGateway에서 TLS 종료
- VirtualService 규칙에 따라 요청을 내부 서비스로 라우팅
- 모든 요청은 oauth2-proxy로 전달되도록 구성
3. oauth2-proxy 접근 (mTLS)
- VirtualService를 통해 전달된 요청은 mTLS로 보호된 내부 통신을 통해 oauth2-proxy Pod에 도달
- oauth2-proxy는 요청에 대한 인증 세션 존재 여부를 확인
4. 사용자 인증 (Keycloak Redirect)
- 인증되지 않은 요청의 경우, oauth2-proxy는 사용자를 Keycloak 로그인 페이지로 리다이렉트
- 사용자는 Keycloak에서 OIDC 기반 인증 수행
5. JWT 발급 및 세션 확립
- 인증 성공 시 Keycloak이 JWT 토큰 발급
- oauth2-proxy는 토큰을 수신하고 브라우저 세션을 유지하며 인증 상태를 관리
6. JWT 전달 및 Istio 인증·인가 정책 적용
- 애플리케이션 Pod의 Envoy Sidecar에서 다음 정책이 적용됨
- RequestAuthentication
- JWT 서명 및 iss 값 검증
- AuthorizationPolicy
- JWT의 groups 클레임 기반 접근 허용/거부 판단
- RequestAuthentication
7. 애플리케이션 처리
- 모든 인증·인가 검증을 통과한 요청만 Spring Boot 애플리케이션으로 전달
- 애플리케이션은 인증·인가 로직 없이 비즈니스 로직만 수행
Sequence Diagram

주요 구성 요소와 역할
- Keycloak
- OIDC 기반 인증 서버(IdP)
- 사용자 인증 후 JWT 토큰 발급
- 토큰에 iss, groups 클레임 포함
- Istio IngressGateway
- 외부 요청의 진입점
- TLS 종료 및 인증 트래픽 진입 제어
- Istio Security Resources
- RequestAuthentication
- JWT 서명 및 클레임 검증
- AuthorizationPolicy
- 요청 경로(Path),
JWT 클레임(groups) 기반 접근 제어
- 요청 경로(Path),
- PeerAuthentication
- Gateway–Pod 및 Pod–Pod 간 트래픽 mTLS 암호화
- RequestAuthentication
- oauth2-proxy
- 브라우저 기반 OIDC 인증 흐름을 처리하는 인증 프록시
- 인증되지 않은 요청은 Keycloak 로그인으로 리다이렉트
- 인증이 완료된 요청에 대해서는 세션을 유지
- Application Pod
- JWT를 직접 검증하지 않음
- Istio에서 검증된 요청만 전달받아 비즈니스 로직 처리
이 구조의 핵심 포인트
- 인증은 Keycloak에서 중앙 집중 관리
- 접근 제어는 Istio Security 정책으로 적용
- 애플리케이션은 보안 로직에서 완전히 분리
4. Istio Security 리소스로 구현한 핵심 정책
이번 구조의 핵심은 인증·인가를 애플리케이션 코드가 아닌 Istio Security 리소스 조합만으로 구현했다는 점이다.
Istio에서는 다음 세 가지 리소스를 중심으로 보안 정책을 구성했다.
4-1. RequestAuthentication – JWT 검증을 애플리케이션 밖에서
RequestAuthentication은 Istio에서 JWT를 검증하기 위한 리소스이다.
이 단계에서 토큰의 유효성이 보장되지 않으면 요청은 애플리케이션까지 도달하지 않는다.
- Keycloak을 issuer로 지정
- JWKS URI를 통해 토큰 서명 검증
- 검증 실패 시 즉시 요청 차단
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
name: sample-authn-ra
namespace: sample-dev
spec:
selector:
matchLabels:
requires-auth: "true"
jwtRules:
- issuer: "https://keycloak.catch-me-app.com/realms/sample-realm"
jwksUri: "https://keycloak.catch-me-app.com/realms/sample-realm/protocol/openid-connect/certs"
4-2. AuthorizationPolicy – 경로·메서드·그룹 기반 인가
JWT가 유효하다고 해서 모든 요청을 허용하지는 않는다.
실제 접근 제어는 AuthorizationPolicy에서 수행된다.
본 환경에서는 다음 두 가지 기준으로 인가 정책을 구성했다.
- 요청 기준
- HTTP Path
- 사용자 기준
- JWT의 groups 클레임
정책 구성 의도
- 특정 경로(/petgradle/owners/new)는 관리자 그룹만 접근 허용
- 그 외 경로(/petgradle/*)는 인증된 사용자라면 접근 허용
이를 위해 AuthorizationPolicy를 두 개로 분리하여 적용했다.
1) 관리자 전용 경로 허용 정책
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-admin-owner-new
namespace: sample-dev
spec:
selector:
matchLabels:
requires-auth: "true"
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["*"]
to:
- operation:
paths:
- /petgradle/owners/new
- /petgradle/owners/new/*
when:
- key: request.auth.claims[groups]
values: ["/admin"]
2) 인증된 사용자용 일반 경로 허용 정책
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-authenticated-petgradle-except-admin
namespace: sample-dev
spec:
selector:
matchLabels:
requires-auth: "true"
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["*"]
to:
- operation:
paths:
- /petgradle
- /petgradle/*
notPaths:
- /petgradle/owners/new
- /petgradle/owners/new/*
이 방식의 장점
- 코드 수정 없이 접근 제어 정책 변경 가능
- 경로 단위로 권한을 명확히 분리 가능
- 정책이 YAML 리소스로 관리되어 변경 이력 추적 및 감사가 용이
4-3. PeerAuthentication – mTLS를 기본값으로
외부 요청뿐 아니라 내부 서비스 간 통신도 동일한 보안 수준으로 보호하기 위해
PeerAuthentication을 사용해 mTLS를 강제했다.
- namespace 단위로 STRICT mTLS 적용
- Gateway ↔ Pod
- Pod ↔ Pod 통신 모두 암호화
이를 통해 다음을 보장할 수 있다.
- 내부망 트래픽 도청 방지
- 서비스 간 호출 위조 차단
- “내부니까 안전하다”는 가정 제거
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: mtls-strict
namespace: sample-dev
spec:
mtls:
mode: STRICT
정리
- RequestAuthentication
- JWT의 유효성 검증 담당
- AuthorizationPolicy
- 누가, 어떤 요청을 할 수 있는지 결정
- PeerAuthentication
- 서비스 간 통신 자체를 안전하게 보호
이 세 가지 리소스를 조합함으로써 인증·인가·암호화를 애플리케이션 외부에서 일관되게 처리할 수 있었다.
5. 동작 검증 시나리오
Keycloak 배포 (Helm + values.yaml)
데모 환경에서는 설치 복잡도를 줄이기 위해 Bitnami Helm Chart를 사용했고,
이미지 태그 이슈 때문에 bitnamilegacy/* 이미지를 사용했다(데모 목적).
또한 실습 편의를 위해 PostgreSQL은 PVC 없이(비영구) 구성했다.
values.yaml (데모용 최소 설정)
global:
security:
allowInsecureImages: true # bitnamilegacy 이미지 사용 (데모 목적)
image:
registry: docker.io
repository: bitnamilegacy/keycloak
tag: 26.3.3-debian-12-r0
proxy: edge
auth:
adminUser: admin
adminPassword: admin123
postgresql:
image:
registry: docker.io
repository: bitnamilegacy/postgresql
tag: 17.6.0-debian-12-r0
primary:
persistence:
enabled: false
readReplicas:
persistence:
enabled: false
ingress:
enabled: true
ingressClassName: nginx
hostname: keycloak.catch-me-app.com
tls: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
설치 명령어
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install keycloak bitnami/keycloak -n keycloak --create-namespace -f values.yaml
Istio 보안 정책과 연동하기 위해 최소한의 Realm / Client / User / Group 설정을 아래와 같이 진행한다.
1. Realm 생성
데모용 Realm인 sample-realm을 생성합니다.
이 Realm은 이후 애플리케이션 접근 시 발급되는 모든 JWT의 issuer(iss) 가 된다.
2. Client 생성 (OIDC Client)
sample-realm 하위에 sample-client를 생성한다.
- Client Type: OpenID Connect
- Client ID: sample-client
- Standard Flow: Enabled
- Access Type: Public
- Redirect URI: https://sample-dev.catch-me-app.com/oauth2/callback
이 Client는 oauth2-proxy를 통해 인증 요청을 처리하며, 로그인 성공 시 authorization code → access token / ID token 교환에 사용된다.
3. User 생성
테스트용 사용자 sample-user, sample-admin 를 생성한다.
- Username / Email 설정
- Password 설정
이 사용자는 이후 실제로 브라우저에서 로그인하여, Istio AuthorizationPolicy가 어떻게 동작하는지 확인하는 데 사용한다.
4. Group 및 권한 모델링
인가 정책을 단순하게 보여주기 위해 Group 기반 권한 모델을 사용한다.
- Group: admin
- sample-admin 사용자에게 admin 그룹을 할당
이 Group 정보는 이후 JWT의 groups 클레임으로 전달되며, Istio AuthorizationPolicy에서 경로 단위 접근 제어 조건으로 사용된다.
5. Group Mapper (JWT Claim 설정)
Client Scope를 통해 사용자의 Group 정보가 JWT에 포함되도록 설정한다.
- Claim name: groups
- Token type: Access Token / ID Token
- Multivalued: true
Istio IngressGateway 노출 (Gateway + VirtualService)
외부 트래픽은 Istio IngressGateway를 단일 진입점으로 사용했다.
Gateway에서 TLS 종료(HTTPS) 와 HTTP→HTTPS 리다이렉트를 처리하고, VirtualService에서 요청을 oauth2-proxy로 라우팅하여 인증 흐름을 강제한다.
Gateway (TLS 종료 + HTTP→HTTPS 리다이렉트)
- 80 포트는 HTTPS로 리다이렉트
- 443 포트에서 TLS를 종료
gateway.yaml
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: auth-gw
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- sample-dev.catch-me-app.com
port:
name: http
number: 80
protocol: HTTP
tls:
httpsRedirect: true
- hosts:
- sample-dev.catch-me-app.com
port:
name: https
number: 443
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: auth-tls
VirtualService (oauth2-proxy로 인증 강제 + 앱 라우팅)
- / 요청은 데모 편의를 위해 /petgradle/로 리다이렉트
- /oauth2/* 는 oauth2-proxy 자체 엔드포인트이므로 oauth2-proxy로 라우팅
- 그 외 모든 경로(/)도 oauth2-proxy로 보내서 “로그인 강제” 흐름을 유지
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: auth-vs
namespace: sample-dev
spec:
gateways:
- istio-system/auth-gw
hosts:
- sample-dev.catch-me-app.com
http:
- match:
- uri:
exact: /
redirect:
uri: /petgradle/
- match:
- uri:
prefix: /oauth2
route:
- destination:
host: oauth2-proxy.sample-dev.svc.cluster.local
port:
number: 80
- match:
- uri:
prefix: /
route:
- destination:
host: oauth2-proxy.sample-dev.svc.cluster.local
port:
number: 80
oauth2-proxy 배포
브라우저 기반 OIDC 로그인 흐름을 애플리케이션 코드 밖에서 처리하기 위해 oauth2-proxy를 별도로 배포했다.
oauth2-proxy.yaml
apiVersion: v1
kind: Service
metadata:
name: oauth2-proxy
namespace: sample-dev
spec:
type: ClusterIP
selector:
app: oauth2-proxy
ports:
- name: http
port: 80
targetPort: 4180
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: oauth2-proxy
namespace: sample-dev
spec:
replicas: 1
selector:
matchLabels:
app: oauth2-proxy
template:
metadata:
labels:
app: oauth2-proxy
spec:
automountServiceAccountToken: false
containers:
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
imagePullPolicy: IfNotPresent
args:
- --provider=oidc
- --oidc-issuer-url=https://keycloak.catch-me-app.com/realms/sample-realm
- --client-id=$(OAUTH2_PROXY_CLIENT_ID)
- --client-secret=$(OAUTH2_PROXY_CLIENT_SECRET)
- --cookie-secret=$(OAUTH2_PROXY_COOKIE_SECRET)
- --cookie-domain=.catch-me-app.com
- --redirect-url=https://sample-dev.catch-me-app.com/oauth2/callback
- --scope=openid email profile
- --reverse-proxy=true
- --pass-access-token=true
- --set-authorization-header=true
- --pass-authorization-header=true
- --oidc-groups-claim=groups
- --scope=openid email profile
- --email-domain=*
- --upstream=http://sample-petgradle-boot-svc.sample-dev.svc.cluster.local:80
- --pass-host-header=false
- --http-address=0.0.0.0:4180
env:
- name: OAUTH2_PROXY_CLIENT_ID
valueFrom:
secretKeyRef:
name: oauth2-proxy
key: client-id
- name: OAUTH2_PROXY_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oauth2-proxy
key: client-secret
- name: OAUTH2_PROXY_COOKIE_SECRET
valueFrom:
secretKeyRef:
name: oauth2-proxy
key: cookie-secret
데모
브라우저를 통해 애플리케이션에 접근한다.
Keycloak으로 리다이렉트되면 생성했던 sample-user로 로그인한다.

메인 화면이 잘 나오는 것을 확인할 수 있다.

인가 정책에서 차단했던 경로로 접근하면 접근 권한이 없다는 메세지를 확인할 수 있다.

이번에는 admin 그룹에 추가했던 sample-admin으로 로그인한다.

메인 화면이 나오면 차단되었던 경로로 접근한다.

이번에는 정상적으로 들어가지는 것을 확인할 수 있다.

6. 실제 서비스 적용 사례 – 공용 Gateway + 서비스별 보안 정책 분리
데모에서는 단일 서비스(/petgradle)에 대해 Keycloak 기반 인증과 Istio 인가 정책을 적용해 보았지만, 실제 업무 환경에서는 훨씬 많은 서비스가 하나의 클러스터에서 공존한다.
회사 클라우드 네이티브 솔루션에서 최근 Istio와 Keycloak을 적용한 사례에서는, Istio Gateway를 공용 진입점으로 두고, 여기서 TLS 종료와 외부 진입 정책을 중앙에서 통합 관리했다. 각 서비스는 Gateway를 따로 만들지 않고, VirtualService로 도메인/경로(prefix) 기반 라우팅 규칙만 추가하는 방식으로 확장했다.
보안 측면에서는 Keycloak이 발급한 JWT를 기준으로, RequestAuthentication에서 JWT 서명/issuer 검증을 수행하고 AuthorizationPolicy에서 경로 및 클레임(예: groups/roles) 기반 접근 제어를 적용했다.
이 구조의 장점은, 서비스가 늘어나도 라우팅과 보안 정책이 YAML로 표준화되어 애플리케이션 코드 수정 없이도 정책 변경/추적/감사가 가능하다는 점이다.
그 결과 인증·인가 로직이 애플리케이션마다 중복 구현되지 않았고, 보안 정책 변경도 “YAML 변경” 수준에서 빠르게 반영할 수 있었다.
이 구조는 단순히 “보안을 강화했다”는 수준을 넘어, 서비스가 늘어나도 운영 복잡도가 폭발하지 않도록 보안과 라우팅을 표준화했다는 점에서 의미가 있었다.
마무리 – “보안은 기능이 아니라 구조였다”
Istio와 Keycloak을 사용하면서 크게 느낀 점은, 인증·인가는 애플리케이션의 기능이 아니라 아키텍처의 구조라는 사실이었다.
과거에는 보안을 구현하려면 자연스럽게 애플리케이션 코드부터 손대게 되었고, 필터나 인터셉터, 미들웨어에 로직이 쌓이면서 서비스마다 서로 다른 방식의 인증·인가가 존재하게 되곤 했다.
인증과 인가를 서비스 바깥으로 분리함으로써, 애플리케이션은 보안 로직에서 벗어나 본래의 역할에만 집중할 수 있었다.
'Keycloak > Istio Security' 카테고리의 다른 글
| Istio 기반 MSA 인증·인가 사용자 JWT로 Kubernetes RBAC을 직접 타는 k8s API 서버 구현 (0) | 2025.11.24 |
|---|---|
| Istio 기반 MSA 인증·인가 Keycloak OIDC & RBAC 연동 구성 (0) | 2025.11.24 |
| Istio 기반 MSA 인증·인가 Keycloak 배포 (0) | 2025.11.06 |
| Istio 기반 MSA 인증·인가 흐름 (0) | 2025.11.04 |