71 lines
3.7 KiB
Markdown
71 lines
3.7 KiB
Markdown
# Keycloak Claim / Role 설계
|
|
|
|
## Why
|
|
|
|
Resource Server는 token을 검증한 뒤 어떤 claim을 내부 principal로 신뢰할지 정해야 합니다.
|
|
모든 claim을 그대로 비즈니스 계층에 넘기면 Keycloak 계약 변경이 내부 로직 전체로 번집니다.
|
|
|
|
## What
|
|
|
|
auth-server가 사용하는 최소 claim은 다음입니다.
|
|
|
|
| Claim | 용도 | 내부 모델 |
|
|
|------|------|-----------|
|
|
| `sub` | Keycloak 사용자 고정 식별자 | `provider_subject` |
|
|
| `email` | 내부 사용자 신규 생성과 충돌 검사 | `UserEmail` |
|
|
| `name` | 내부 사용자 표시 이름 | `UserName` |
|
|
| `preferred_username` | `name`이 없을 때 fallback | `AuthenticatedUser.name` |
|
|
| `scope` | OAuth2 scope authority | `SCOPE_*` |
|
|
| `realm_access.roles` | Realm role authority | `ROLE_*` |
|
|
|
|
## Sample JWT payload
|
|
|
|
Keycloak realm `platform` 이 발급하는 access token 의 payload 는 다음 형태입니다 (값은 예시).
|
|
|
|
```json
|
|
{
|
|
"iss": "https://keycloak.dev.example.com/realms/platform",
|
|
"sub": "1f7a3b2e-9c4d-4f81-a0e7-2b8f5c1d6a4b",
|
|
"aud": "auth-server-ingress",
|
|
"exp": 1735689600,
|
|
"iat": 1735686000,
|
|
"azp": "auth-server-ingress",
|
|
"scope": "openid email profile",
|
|
"email": "alice@example.com",
|
|
"email_verified": true,
|
|
"name": "Alice Kim",
|
|
"preferred_username": "alice",
|
|
"realm_access": {
|
|
"roles": ["user"]
|
|
},
|
|
"resource_access": {
|
|
"auth-server-ingress": { "roles": [] }
|
|
}
|
|
}
|
|
```
|
|
|
|
`sub` 는 Keycloak 이 사용자에게 부여하는 immutable UUID 로, 내부 DB 의 `provider_subject` 와 1:1 로 묶입니다. `realm_access.roles` 는 위 표의 `ROLE_*` 매핑 대상입니다.
|
|
|
|
## How
|
|
|
|
`KeycloakJwtAuthenticationConverter`가 JWT claim을 `AuthenticatedUser`로 변환합니다.
|
|
controller는 `@CurrentUser`로 이 principal을 받고, application에는 `KeycloakUserClaims(subject, email, name)`만 전달합니다.
|
|
`KeycloakUserClaimsValidator`는 claim이 비어 있거나 `UserEmail`/`UserName` 도메인 규칙에 어긋나면 `InvalidKeycloakClaimsException(AUTH-003, HTTP 400)`으로 번역해 Keycloak 오염 클레임이 비즈니스 로직까지 흘러들지 않게 차단합니다.
|
|
|
|
권장 realm role:
|
|
|
|
| Keycloak role | Spring authority | 용도 |
|
|
|---------------|------------------|------|
|
|
| `user` | `ROLE_user` | 일반 인증 사용자 |
|
|
| `admin` | `ROLE_admin` | 운영/관리 API |
|
|
|
|
역할 이름은 Keycloak realm에서 소유합니다. auth-server는 role 값을 새로 발급하거나 DB 값으로 권한을 덮어쓰지 않습니다.
|
|
현재 `/api/v1/auth/me`는 `GET` 요청에 대해 `ROLE_user`가 필요합니다. 따라서 Keycloak realm의 self-service registration 기본 역할에는 `user`를 포함해야 하며, 역할이 없는 token은 인증은 성공하더라도 `AUTH-002`로 거절됩니다.
|
|
|
|
## Result / Trade-offs
|
|
|
|
- token 검증 결과는 신뢰하되, 내부 사용자 식별은 `(provider=KEYCLOAK, provider_subject=sub)` 기준으로만 수행합니다.
|
|
- email은 자동 계정 연결 키로 쓰지 않습니다. 연결된 내부 사용자가 없으면 email 충돌 검사 후 새 내부 사용자를 만들고, 같은 email 이 이미 다른 subject 에 연결되어 있으면 `AUTH-005` 409를 반환합니다.
|
|
- 실무 권장안은 “IdP subject를 immutable external identity로 삼고, email은 표시/연락/초기 등록 보조값으로만 다루는 것”입니다. email은 변경될 수 있고 재사용될 수 있으므로 권한 있는 내부 계정 연결 키로 쓰면 위험합니다.
|
|
- 표시용 `email`/`name`은 token claim에서 왔더라도 DB에 저장된 값이 authoritative입니다. 따라서 `/api/v1/auth/me` 응답은 DB 값을 노출하고, 권한(`authorities`)은 token claim에서 그대로 가져옵니다.
|