라우트 레벨 보안의 개념
Camel에서 보안은 두 가지 수준에서 적용할 수 있습니다. 첫째는 네트워크/전송 수준(SSL, VPN)이고, 둘째는 라우트 수준입니다. 라우트 수준에서는 누가 어떤 라우트를 실행할 수 있는지, 어떤 데이터에 접근할 수 있는지를 제어합니다.
RoutePolicy로 라우트 동작 제어
RoutePolicy는 라우트의 시작/중지/일시 정지를 제어하는 인터페이스입니다. 보안 정책을 이 인터페이스를 구현해 적용할 수 있습니다.
public class ThrottlingRoutePolicy extends RoutePolicySupport {
private final RateLimiter rateLimiter = RateLimiter.create(100); // 초당 100회
@Override
public void onExchangeBegin(Route route, Exchange exchange) {
if (!rateLimiter.tryAcquire()) {
exchange.setException(new TooManyRequestsException("요청 한도 초과"));
}
}
}
// 라우트에 적용
from("direct:api")
.routePolicy(new ThrottlingRoutePolicy())
.to("bean:service");
역할 기반 접근 제어 (RBAC)
사용자 역할에 따라 라우트 접근을 제한하는 패턴입니다.
from("direct:adminOperation")
.process(exchange -> {
String role = exchange.getIn().getHeader("userRole", String.class);
if (!"ADMIN".equals(role)) {
throw new AccessDeniedException("관리자 권한 필요");
}
})
.to("bean:adminService");
from("direct:readOperation")
.process(exchange -> {
String role = exchange.getIn().getHeader("userRole", String.class);
if (!Arrays.asList("ADMIN", "USER", "READ_ONLY").contains(role)) {
throw new AccessDeniedException("권한 없음");
}
})
.to("bean:readService");
감사 로깅 (Audit Logging)
보안 요구사항으로 누가 무엇을 언제 했는지 기록해야 합니다. Wire Tap과 이벤트 저장소를 결합합니다.
// 모든 민감한 작업에 감사 로그 추가
from("direct:sensitiveOperation")
.process(exchange -> {
exchange.getIn().setHeader("auditUserId",
SecurityContextHolder.getContext().getAuthentication().getName());
exchange.getIn().setHeader("auditTimestamp", LocalDateTime.now().toString());
exchange.getIn().setHeader("auditAction", "sensitiveOperation");
})
.wireTap("direct:writeAuditLog")
.to("bean:sensitiveService");
from("direct:writeAuditLog")
.to("sql:INSERT INTO audit_log (user_id, action, timestamp, payload) "
+ "VALUES (:#${header.auditUserId}, :#${header.auditAction}, "
+ ":#${header.auditTimestamp}, :#${body})");
IP 화이트리스트 필터링
from("netty-http:http://0.0.0.0:8080/api")
.process(exchange -> {
String clientIp = exchange.getIn()
.getHeader("CamelNettyRemoteAddress", String.class);
if (!allowedIps.contains(clientIp)) {
exchange.getIn().setHeader(Exchange.HTTP_RESPONSE_CODE, 403);
exchange.getIn().setBody("접근 거부");
exchange.setRouteStop(true);
}
})
.to("bean:apiService");
보안 테스트
보안 기능은 반드시 테스트해야 합니다. 인증 실패, 권한 부족, SQL 인젝션 시도 등 다양한 보안 시나리오를 테스트합니다.
@Test
public void testUnauthorizedAccess() {
Exchange result = template.request("direct:adminOperation", exchange -> {
exchange.getIn().setHeader("userRole", "USER");
exchange.getIn().setBody("test");
});
assertNotNull(result.getException());
assertThat(result.getException()).isInstanceOf(AccessDeniedException.class);
}