Files
llm-wiki/raw/interviews/clean-architecture-method-authorization-without-spring-coupling-2026-06-08.md
T

3.7 KiB

title, source_type, status, related_branches, related_projects, tags, created
title source_type status related_branches related_projects tags created
interview / Clean Architecture 에서 Spring 결합 없이 method-level 인가 거는 법 interview raw
feature-authentication-authorization-contract
ca-skeleton
interview
ca-skeleton
security
authorization
clean-architecture
spring-security
2026-06-08

interview: framework-free method authorization (authz contract)

Layer: raw/interviews/ — feature-authentication-authorization-contract 구현에서 정직하게 도출되는 면접 질문/답.

Parent / 부모

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#handleForbiddenSecurityErrorClassifier.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 GrantedAuthorityROLE_+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.