리소스 스캐너
애플리케이션 시작 시 @Protectable 메서드와 MVC 엔드포인트를 자동으로 스캔해, 정책 연결 대상인 ManagedResource 엔티티로 등록하는 리소스 발견 계층입니다.
개요
Contexa는 애플리케이션에서 보호 가능한 리소스를 서로 다른 방식으로 수집하는 두 종류의 스캐너를 제공합니다.
- MethodResourceScanner —
@Protectable가 붙은 비컨트롤러 public 메서드를 발견합니다. - MvcResourceScanner —
RequestMappingHandlerMapping을 사용해 Spring MVC 엔드포인트를 발견합니다.
두 스캐너 모두 ResourceScanner 인터페이스를 구현하며, 초기 상태가 NEEDS_DEFINITION인 ManagedResource 엔티티를 생성합니다.
엔드투엔드 파이프라인
리소스 스캔은 4단계 리소스 관리 파이프라인의 시작점입니다: Scan — Define — Policy — Dynamic Authorization. 애플리케이션 시작 시 ResourceScanner 구현체가 모든 @RequestMapping 엔드포인트와 @Protectable 메서드를 찾아 DB의 ManagedResource로 동기화합니다. 이후 AI 엔진이 발견된 리소스에 대해 사람이 읽기 쉬운 friendly name을 일괄 생성합니다.
발견된 리소스는 ManagedResourceRepository에 동기화되고, 현재 OSS 기준의 정책 작성 표면에서 검토됩니다. 현재 템플릿 트리에서 리소스 검토와 정책 생성의 중심은 Policy Center입니다. 서버 코드에는 레거시 /admin/workbench/resources 경로가 남아 있지만, 독립 Thymeleaf 템플릿은 OSS 트리에 없습니다. 정책이 만들어지면 CustomDynamicAuthorizationManager가 .anyRequest().access(customDynamicAuthorizationManager)를 통해 런타임에 이를 집행합니다.
ResourceScanner 인터페이스
public interface ResourceScanner {
List<ManagedResource> scan();
}
MethodResourceScanner
io.contexa.contexaiam 패키지의 Spring 빈을 스캔해 @Protectable가 붙은 public 메서드를 찾습니다. 컨트롤러 클래스는 MvcResourceScanner가 담당하므로 명시적으로 제외합니다.
스캔 로직
ApplicationContext의 모든 빈 정의를 순회합니다.AopUtils.getTargetClass()로 실제 대상 클래스를 해석합니다.io.contexa.contexaiam패키지 밖의 클래스는 건너뜁니다.@Controller,@RestController클래스는 건너뜁니다.- public
@Protectable메서드마다ManagedResource를 생성합니다.
리소스 식별자 형식
메서드 리소스는 완전 수식 메서드 시그니처를 식별자로 사용합니다.
io.contexa.contexaiam.service.AccountService.updateAccount(Long,AccountDto)
ManagedResource 필드 (METHOD 타입)
| 필드 | 값의 출처 |
|---|---|
resourceIdentifier | 파라미터 타입을 포함한 완전 수식 메서드 시그니처 |
resourceType | METHOD |
serviceOwner | 선언 클래스의 단순 클래스명 |
parameterTypes | 완전 수식 파라미터 타입 이름의 JSON 배열 |
returnType | 완전 수식 반환 타입 이름 |
sourceCodeLocation | 클래스명으로부터 유도한 경로: io/contexa/.../ClassName.java |
status | NEEDS_DEFINITION |
MvcResourceScanner
Spring MVC의 @Controller, @RestController 클래스를 스캔해 URL 매핑 엔드포인트를 수집합니다. 이때 RequestMappingHandlerMapping을 사용합니다.
스캔 로직
requestMappingHandlerMapping빈을 조회합니다.- 등록된 모든 핸들러 메서드를 순회합니다.
io.contexa.contexaiam패키지 밖 클래스는 건너뜁니다.@Controller또는@RestController가 없는 클래스는 건너뜁니다.PathPatternsRequestCondition에서 URL 패턴을 추출합니다.- HTTP 메서드를 추출하고, 지정되지 않았으면
ANY를 사용합니다. IamAdminProperties.restDocsPath를 이용해 API 문서 URL을 만듭니다.
ManagedResource 필드 (URL 타입)
| 필드 | 값의 출처 |
|---|---|
resourceIdentifier | URL 패턴 문자열 (예: /admin/users/{id}) |
resourceType | URL |
httpMethod | GET, POST, PUT, DELETE, PATCH, 또는 ANY |
friendlyName | 핸들러 메서드명 |
description | URL: [HTTP_METHOD] /path/pattern |
serviceOwner | 컨트롤러의 단순 클래스명 |
apiDocsUrl | {restDocsPath}#{controller}_{method} |
status | NEEDS_DEFINITION |
ManagedResource 엔티티
@Entity
@Table(name = "MANAGED_RESOURCE")
public class ManagedResource {
private Long id;
private String resourceIdentifier; // URL pattern or method signature
private ResourceType resourceType; // URL or METHOD
private HttpMethod httpMethod; // GET, POST, PUT, DELETE, PATCH, ANY
private String friendlyName;
private String description;
private String serviceOwner;
private String parameterTypes; // JSON array (METHOD only)
private String returnType; // Fully qualified type (METHOD only)
private String apiDocsUrl; // Link to API documentation
private String sourceCodeLocation; // Source file path (METHOD only)
private String availableContextVariables;
private Status status; // NEEDS_DEFINITION, PERMISSION_CREATED,
// POLICY_CONNECTED, EXCLUDED
private Permission permission; // OneToOne mapping
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public enum ResourceType { URL, METHOD }
public enum HttpMethod { GET, POST, PUT, DELETE, PATCH, ANY }
public enum Status {
NEEDS_DEFINITION,
PERMISSION_CREATED,
POLICY_CONNECTED,
EXCLUDED
}
}
리소스 생명주기
| 상태 | 설명 |
|---|---|
NEEDS_DEFINITION | 스캐너가 탐지했지만 아직 권한이 연결되지 않은 상태 |
PERMISSION_CREATED | Permission 엔티티가 이 리소스에 연결된 상태 |
POLICY_CONNECTED | 정책이 target을 통해 이 리소스를 참조하는 상태 |
EXCLUDED | 인가 관리에서 명시적으로 제외된 상태(예: 공개 엔드포인트) |
ManagedResource 생명주기
상태 전이
defineResourceAsPermission() 호출로 권한이 생성된 상태
| 상태 | 설명 | 전이 트리거 |
|---|---|---|
NEEDS_DEFINITION | 스캐너가 리소스를 발견했지만 아직 구성되지 않은 상태 | 초기 스캔 |
PERMISSION_CREATED | defineResourceAsPermission() 호출로 Permission 엔티티가 생성된 상태 | Policy Center 리소스 검토 흐름의 관리자 동작 |
POLICY_CONNECTED | 정책이 생성되어 이 리소스와 연결된 상태 | Policy Center 정책 작성 흐름 |
EXCLUDED | 인가 관리 대상에서 제외된 리소스 | 관리자가 제외 처리 |
ManagedResource 엔티티 필드
| 필드 | 타입 | 설명 |
|---|---|---|
resourceIdentifier | String | 고유 식별자(URL 패턴 + HTTP 메서드 또는 정규화된 메서드 시그니처) |
resourceType | ResourceType | URL 또는 METHOD |
friendlyName | String | AI 제안 이름 또는 AI 제안을 사용할 수 없을 때의 대체 표시 이름 |
status | ManagedResource.Status | NEEDS_DEFINITION, PERMISSION_CREATED, POLICY_CONNECTED, EXCLUDED |
리소스 검토 흐름
레거시 /admin/workbench/resources 경로는 컨트롤러 계층에 남아 있지만, 현재 OSS 템플릿 트리에서는 리소스 관리가 Policy Center를 중심으로 이루어집니다. 이 섹션은 그 화면이 표현하는 실제 리소스 관리 흐름을 설명합니다.
리소스 발견
- 상태 기반 필터링으로 스캔된 리소스를 모두 조회
- 필요할 때 스캔 결과를 다시 동기화
- 리소스 유형, 상태, 이름 기준으로 검색 및 필터링
리소스를 권한으로 정의
NEEDS_DEFINITION 상태의 리소스에서 "Define as Permission"을 선택하면 다음 순서로 진행됩니다.
- 대응되는
Permission엔티티가 생성됩니다. - 리소스 상태가
PERMISSION_CREATED로 변경됩니다. - 이 리소스를 정책 작성 흐름에서 사용할 수 있게 됩니다.
리소스 검토 이후의 두 경로
리소스를 권한으로 정의한 뒤에는 현재 Policy Center UI에서 두 가지 정책 생성 경로를 사용할 수 있습니다.
| 경로 | 동작 | 용도 |
|---|---|---|
| 빠른 모드 | 선택한 역할과 권한으로 기본 ALLOW 정책을 생성 | 단순한 역할-권한 매핑 |
| 수동 / AI 모드 | 선택한 리소스 맥락이 미리 채워진 현재 정책 작성 흐름을 엶 | 복잡한 조건, AI 표현식, 다중 규칙 정책 |
End-to-End 워크플로우
1단계: 스캔
WorkbenchInitializer가 애플리케이션 시작 시 자동으로 동작합니다. MvcResourceScanner는 모든 @Controller와 @RestController 클래스를 스캔하고, MethodResourceScanner는 모든 @Protectable 메서드를 스캔합니다. 이후 AI 제안을 사용할 수 있으면 표시 이름과 설명을 보강하고, 사용할 수 없으면 대체 값을 유지합니다.
2단계: 검토 및 정의
Policy Center에서 NEEDS_DEFINITION 상태의 리소스를 검토합니다. 인가가 필요한 리소스는 권한으로 정의하고, 정적 자산이나 공개 엔드포인트처럼 인가가 필요하지 않은 리소스는 제외할 수 있습니다.
3단계: 정책 작성
리소스 검토 영역에서 정책 작성을 시작하면, 선택한 리소스 맥락이 정책 작성 흐름에 전달됩니다. 호환 가능한 조건 템플릿만 노출되고, 정책 생성은 현재 OSS 기준으로 Policy Center 기반 흐름에서 진행됩니다.
4단계: 동적 인가 반영
정책이 저장되면 reloadAuthorizationSystem()이 CustomDynamicAuthorizationManager.reload()를 호출하고, 새 정책이 즉시 반영됩니다. 보안 설정에서 .anyRequest().access(customDynamicAuthorizationManager)를 사용하면 별도의 정적 인가 규칙 없이 동적으로 정책을 평가할 수 있습니다.
리소스 검토 운영 가이드
리소스 관리 흐름의 중심은 현재 Policy Center입니다. 서버 코드에는 레거시 /admin/workbench/resources 컨트롤러 경로가 남아 있지만, 실제 OSS 운영 흐름은 Policy Center에서 리소스를 검토하고 권한 및 정책에 연결하는 방식으로 정리됩니다.
리소스 필터링
리소스 검토 화면에서는 여러 필터를 사용해 필요한 리소스를 빠르게 찾을 수 있습니다.
| 필터 | 옵션 | 용도 |
|---|---|---|
| 상태별 | NEEDS_DEFINITION, PERMISSION_CREATED, POLICY_CONNECTED, EXCLUDED | 특정 라이프사이클 단계의 리소스를 찾습니다. |
| 서비스 소유자별 | 컨트롤러 또는 서비스 클래스명 | 특정 모듈에 속한 리소스를 좁혀 봅니다. |
| 키워드 | 자유 텍스트 검색 | 리소스 식별자, 표시 이름, 설명으로 검색합니다. |
| 리소스 유형 | URL 또는 METHOD | 엔드포인트 리소스와 메서드 리소스를 구분합니다. |
리소스 정의
리소스를 정의하면 다음 순서로 처리됩니다.
NEEDS_DEFINITION상태의 리소스에서 "Define as Permission"을 선택합니다.- 권한의 표시 이름을 입력하거나 AI가 제안한 이름을 확인합니다.
Permission엔티티가 생성되고 해당 리소스와 연결됩니다.- 리소스 상태가
PERMISSION_CREATED로 바뀝니다. - 이후 정책 연결 또는 정책 작성 흐름으로 넘어갈 수 있습니다.
리소스 검토에서 정책 연결
리소스를 정의한 뒤에는 현재 UI에서 두 가지 정책 작성 경로를 사용할 수 있습니다.
빠른 생성 모드
선택한 권한과 역할을 이용해 단순한 ALLOW 정책을 빠르게 만듭니다.
- 선택한 리소스 권한을 미리 채웁니다.
- 대상 역할을 선택하고 즉시 저장합니다.
- 간단한 ALLOW 정책을 생성합니다.
수동 / AI 모드
선택한 리소스 맥락을 유지한 채 현재 정책 작성 흐름을 엽니다.
- 타깃 리소스가 미리 채워집니다.
- 조건 템플릿 전체를 선택할 수 있습니다.
- 복잡한 규칙과 AI 조건을 함께 사용할 수 있습니다.
리소스 제외와 복원
인가가 필요하지 않은 리소스(공개 엔드포인트, 정적 자산, 헬스 체크)는 제외할 수 있습니다.
- 제외:
NEEDS_DEFINITION리소스에서 "Exclude"를 선택하면 상태가EXCLUDED로 바뀌고 인가 관리 대상에서 빠집니다. - 복원: 제외된 리소스는 "Restore"를 선택해 다시
NEEDS_DEFINITION상태로 되돌릴 수 있습니다.
제외가 적합한 경우: 공개 API 엔드포인트, 정적 자산 경로, 헬스 체크 엔드포인트(/actuator/health), 문서 경로.
복원이 적합한 경우: 예전에 공개였지만 이제 인가가 필요한 엔드포인트, 또는 실수로 제외된 리소스.
상태 전이 다이어그램
AI 자동 이름 생성
리소스가 스캐너에 의해 발견되면 기술적인 식별자(URL 패턴 또는 메서드 시그니처)는 사람이 바로 이해하기 어렵습니다. AI 자동 이름 생성 시스템은 이 식별자를 바탕으로 사람이 읽기 쉬운 이름과 설명을 자동으로 만듭니다.
동작 방식
ResourceRegistryServiceImpl는 스캔이 끝난 뒤 AI ResourceNaming 서비스를 호출합니다. AI는 각 기술 식별자를 분석해 다음 정보를 생성합니다.
- 표시 이름: 비즈니스 관점에서 이해하기 쉬운 짧은 이름(예:
PUT /api/users/{id}-> "Update User Profile") - 설명: 해당 리소스가 수행하는 작업을 더 자세히 설명한 문장
배치 처리
AI API 사용을 최적화하기 위해 자동 이름 생성은 한 배치당 5개 리소스씩 처리합니다. 이 방식은 다음 장점이 있습니다.
- AI API 호출 횟수를 줄입니다.
- 같은 배치 안의 관련 리소스를 함께 보며 더 일관된 이름을 생성할 수 있습니다.
- 실패가 발생해도 개별 배치 단위로 재시도할 수 있습니다.
이름 생성 전략
| 기술 식별자 | AI 생성 표시 이름 |
|---|---|
GET /api/users | List All Users |
PUT /api/users/{id} | Update User Profile |
DELETE /admin/roles/{id} | Delete Role |
AccountService.updateAccount(Long,AccountDto) | Update Account Details |