42 lines
3.7 KiB
Markdown
42 lines
3.7 KiB
Markdown
---
|
|
title: interview / Clean Architecture 에서 Spring 결합 없이 method-level 인가 거는 법
|
|
source_type: interview
|
|
status: raw
|
|
related_branches: [feature-authentication-authorization-contract]
|
|
related_projects: [ca-skeleton]
|
|
tags: [interview, ca-skeleton, security, authorization, clean-architecture, spring-security]
|
|
created: 2026-06-08
|
|
---
|
|
|
|
# interview: framework-free method authorization (authz contract)
|
|
|
|
> Layer: `raw/interviews/` — feature-authentication-authorization-contract 구현에서 정직하게 도출되는 면접 질문/답.
|
|
|
|
## Parent / 부모
|
|
|
|
- [[raw/branch-notes/feature-authentication-authorization-contract]]
|
|
|
|
## Q1. 왜 `@PreAuthorize` 대신 use-case `AuthorizationPort` 를 만들었나?
|
|
|
|
`@PreAuthorize` 는 SpEL + Spring Security 타입에 bean 을 결합시킨다. application/domain layer 는 framework-free 여야 하므로(project §5, `TransactionPort` 선례) 인가 *결정* 을 plain Java port(`AuthorizationPort.requirePermission(AuthorizationPrincipal, Permission)`)로 표현하고, *집행 메커니즘* 만 adapter 의 custom `AuthorizationManager<MethodInvocation>` 에 둔다. 결과: use case 는 `@RequiresPermission("worklog:close")`(Spring-free annotation)만 선언, 집행은 adapter. 트레이드오프: `@PreAuthorize` 대비 boilerplate(annotation manager + advisor wiring) ↑, 대신 layer 순수성 유지.
|
|
|
|
## Q2. permission 중심 RBAC 를 택한 이유? OWASP 는 ABAC 를 권한다는데?
|
|
|
|
permission(`resource:action`)=집행 단위, role=permission 묶음. 도메인이 role 을 추가해도 enforcement 코드는 불변(role→permission registry 만 갱신). OWASP 는 dynamic attribute 가 필요하면 ABAC 를 선호하지만(OWASP-PM-C1), 정적 permission + 소수 role 규모에선 YAGNI. 핵심: `AuthorizationPort` 인터페이스가 ABAC 전환 path 를 보장 — 구현체만 owner/relationship predicate 로 교체하면 됨.
|
|
|
|
## Q3. 인가 거부를 어떻게 403 으로 내보내나? (2-hop)
|
|
|
|
application port 는 Spring-free 라 Spring `AccessDeniedException` 을 못 던진다. (1) port 가 자체 `AuthorizationDeniedException`(RuntimeException) throw → (2) adapter 의 `AuthorizationManager` 가 이를 잡아 `AuthorizationDecision(false)` 반환 → Spring method-security interceptor 가 `AccessDeniedException` 발생 → `GlobalExceptionHandler#handleForbidden` 가 `SecurityErrorClassifier.classifyAccessDenied` 위임 → `AUTHZ_INSUFFICIENT_PERMISSION`(403). error code SSOT 는 security-baseline, 본 계약은 emission producer.
|
|
|
|
## Q4. AOP proxy bypass 위험은?
|
|
|
|
method security 는 Spring AOP proxy 기반이라 self-invocation(같은 객체 내부 호출)이나 non-Spring-bean 호출은 advisor 를 우회한다. 또 concrete 타입 주입은 CGLIB(`proxyTargetClass=true`) 여야 proxy 가 subtype 이 된다(→ [[raw/errors/method-security-cglib-vs-jdk-proxy-usecase-injection-2026-06-08]]). 보강: 모든 mutating 진입점이 Spring bean 경유인지 ArchUnit 정적 검증(host=architecture-enforcement-rules).
|
|
|
|
## Q5. role registry key 를 `ROLE_ADMIN` 으로 안 쓰고 raw `admin` 으로 쓴 이유?
|
|
|
|
`AuthenticatedUser.roles` 는 IdP 원본 raw role(prefix 없음)을 담고, Spring `GrantedAuthority` 만 `ROLE_`+upper prefix 를 받는다. application-core 는 Spring-free 라 `GrantedAuthority` 가 아니라 raw role set 을 consume → registry key = raw role(lowercase 정규화, case-insensitive). 잘못해서 `ROLE_ADMIN` 으로 조회하면 0 권한 fail-closed.
|
|
|
|
## Q6. unauthenticated vs unauthorized 구분?
|
|
|
|
인증 없음 → method-security 의 deferred auth supplier 가 `AuthenticationException`(401-family). 권한 부족 → `AccessDeniedException`(403). prod 는 filter chain 이 미인증을 401 로 먼저 차단하므로 method-security 의 미인증 경로는 backstop.
|