지혜를찾아 블로깅

React + SpringBoot 조합에서 고려해야할 사항들. 본문

IT Story

React + SpringBoot 조합에서 고려해야할 사항들.

지혜를찾는사람 2025. 6. 11. 13:14
반응형

React + SpringBoot 조합에서 고려해야할 사항들.

StringFramework, 전자정부프레임워크 구성보다는 이제는 React+Nodejs, React+SpringBoot 구성이 대세인 시대가되었다. 오늘은 고려해야할 것들에 대해서 알아보겠습니다.

1. RESTful API (Representational State Transfer)

설명: REST는 오래전부터 웹 서비스를 위한 가장 일반적이고 널리 사용되고 안정된 아키텍처 스타일입니다. 자원(Resource)을 중심으로 설계되며, HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용하여 자원을 조작합니다. Spring Boot는 백앤드에서 RESTful API 개발에 매우 강력한 지원을 제공합니다.

장점:

  • 단순성 및 범용성: HTTP 표준을 따르므로 이해하고 사용하기 쉽습니다. 대부분의 개발자가 친숙합니다.
  • 캐싱 용이: HTTP 캐싱 메커니즘을 활용하여 성능을 향상시킬 수 있습니다.
  • 스테이트리스(Stateless): 서버가 클라이언트의 상태를 저장하지 않으므로, 서버 확장이 용이하고 안정성이 높습니다.
  • 다양한 클라이언트 지원: 웹 브라우저, 모바일 앱 등 다양한 클라이언트에서 쉽게 접근할 수 있습니다.
  • Spring Boot와의 시너지: Spring Boot는 @RestController, @GetMapping, @PostMapping 등 RESTful API 개발을 위한 어노테이션과 기능을 풍부하게 제공하여 개발 생산성을 높입니다.

단점:

  • 오버페칭(Over-fetching) 및 언더페칭(Under-fetching):
    • 오버페칭: 클라이언트가 필요한 데이터보다 더 많은 데이터를 받는 경우 (예: 사용자 목록을 요청했는데 모든 사용자 정보가 오는 경우)
    • 언더페칭: 클라이언트가 필요한 데이터를 한 번의 요청으로 받지 못하고 여러 번의 요청을 해야 하는 경우 (예: 게시물과 게시물 작성자 정보를 따로 요청해야 하는 경우)
    • 이러한 문제는 특히 복잡한 데이터 구조에서 발생할 수 있습니다.
  • 복잡한 쿼리 처리의 어려움: 클라이언트가 원하는 데이터 구조를 유연하게 구성하기 어렵습니다.

React + Spring Boot에 적합한 경우:

  • 대부분의 웹 애플리케이션에 적합합니다.
  • 데이터 구조가 비교적 명확하고, 자원 중심의 CRUD(Create, Read, Update, Delete) 작업이 많은 경우.
  • 빠른 개발 및 쉬운 유지보수가 중요한 경우.
  • 공개 API를 제공해야 하는 경우.

API 설계 시 고려사항:

  • 명확한 URI 설계: 자원을 명확하게 표현하는 명사 형태의 URI 사용 (예: /users, /products/{id})
  • 적절한 HTTP 메서드 사용: 각 동작에 맞는 HTTP 메서드 사용 (GET-조회, POST-생성, PUT/PATCH-수정, DELETE-삭제)
  • 상태 코드 활용: HTTP 상태 코드(200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Internal Server Error 등)를 통해 API 호출 결과를 명확히 전달.
  • 일관된 응답 형식: JSON (또는 XML)과 같은 표준 형식 사용.
  • 버전 관리: v1, v2 등으로 API 버전을 관리하여 하위 호환성 유지.
  • 오류 처리: 일관된 오류 응답 형식 제공.
그이외의 방안들은:

2. GraphQL

설명: GraphQL은 API를 위한 쿼리 언어이자 런타임입니다. 클라이언트가 필요한 데이터를 정확하게 요청하고, 서버는 요청한 데이터만 반환합니다. 단일 엔드포인트에서 모든 데이터를 가져올 수 있습니다.

장점:

  • 유연한 데이터 페칭: 클라이언트가 필요한 필드만 선택하여 요청할 수 있어 오버페칭/언더페칭 문제를 해결합니다.
  • 단일 엔드포인트: 모든 데이터 요청이 하나의 엔드포인트로 이루어져 API 관리가 간편합니다.
  • 빠른 클라이언트 개발: 클라이언트 개발자가 백엔드에 의존하지 않고 필요한 데이터를 즉시 정의할 수 있습니다.
  • 타입 시스템: 강력한 타입 시스템을 통해 API 스키마를 정의하고 유효성을 검사합니다.

단점:

  • 학습 곡선: REST보다 개념적으로 복잡하고, 새로운 쿼리 언어 및 스키마 정의에 대한 학습이 필요합니다.
  • 파일 업로드/다운로드 처리: REST에 비해 파일 업로드/다운로드 처리가 다소 복잡할 수 있습니다.
  • 캐싱 복잡성: 단일 엔드포인트와 유연한 쿼리 특성 때문에 HTTP 캐싱을 직접적으로 적용하기 어렵습니다.
  • N+1 쿼리 문제: Resolver 설계에 따라 N+1 쿼리 문제가 발생할 수 있으며, 이를 해결하기 위한 DataLoader와 같은 추가적인 노력이 필요합니다.
  • 서버 복잡성 증가: 스키마 정의, Resolver 구현 등 백엔드에서 구현해야 할 부분이 REST에 비해 많아질 수 있습니다.

React + Spring Boot에 적합한 경우:

  • 데이터 요구사항이 복잡하고 자주 변경되는 경우.
  • 여러 데이터 소스를 조합하여 클라이언트에게 제공해야 하는 경우.
  • 모바일 앱처럼 대역폭 최적화가 중요한 경우.
  • 프론트엔드 팀이 백엔드 팀과 독립적으로 빠르게 개발해야 하는 경우.
  • 실시간 업데이트 (Subscriptions)가 필요한 경우 (예: 채팅, 알림).

Spring Boot에서 GraphQL 구현: graphql-java-spring-boot-starter와 같은 라이브러리를 사용하여 Spring Boot에서 GraphQL 서버를 쉽게 구축할 수 있습니다. Spring Data JPA 등과 연동하여 스키마 및 Resolver를 구현합니다.


3. gRPC (Google Remote Procedure Call)

설명: gRPC는 Google에서 개발한 고성능 RPC(Remote Procedure Call) 프레임워크입니다. Protocol Buffers를 사용하여 서비스 인터페이스와 메시지 형식을 정의하고, HTTP/2를 기반으로 통신합니다.

장점:

  • 고성능: HTTP/2의 멀티플렉싱, 스트림, 헤더 압축 등의 기능을 활용하고 Protocol Buffers를 통한 이진 직렬화로 매우 빠른 통신 속도와 낮은 대역폭 사용량을 제공합니다.
  • 강력한 타입 시스템: Protocol Buffers를 통해 서비스와 메시지의 스키마를 엄격하게 정의하여 런타임 오류를 줄입니다.
  • 코드 생성: proto 파일에서 다양한 언어로 클라이언트 및 서버 코드를 자동으로 생성하여 개발 생산성을 높입니다.
  • 양방향 스트리밍: 클라이언트-서버 간 양방향 스트리밍을 지원하여 실시간 통신 및 스트리밍 서비스에 적합합니다.
  • 폴리그랏(Polyglot) 환경에 적합: 여러 언어로 구성된 마이크로서비스 아키텍처에 특히 유용합니다.

단점:

  • 브라우저 지원 제한: 웹 브라우저에서 gRPC를 직접 호출하는 것은 쉽지 않습니다. gRPC-Web과 같은 프록시 계층이 필요합니다.
  • 복잡성: REST나 GraphQL에 비해 학습 곡선이 가파르고 설정 및 구현이 더 복잡합니다.
  • 사람이 읽기 어려운 형식: Protocol Buffers는 이진 형식으로, 사람이 직접 읽고 디버깅하기 어렵습니다.
  • REST/GraphQL에 비해 커뮤니티 및 생태계가 작음: 특히 웹 프론트엔드 영역에서는 아직 REST/GraphQL만큼의 풍부한 자료나 도구가 부족할 수 있습니다.

React + Spring Boot에 적합한 경우:

  • 마이크로서비스 간 통신: 백엔드 내부의 서비스 간 고성능 통신이 필요한 경우.
  • 성능이 매우 중요한 애플리케이션: 실시간 데이터 처리, 대용량 데이터 전송 등.
  • 스트리밍이 필요한 경우: 실시간 알림, 라이브 스트리밍 등.
  • React에서 gRPC를 사용하려면 일반적으로 gRPC-Web을 통해 HTTP/1.1 환경에서 gRPC 서비스를 호출할 수 있도록 프록시 계층(예: Envoy, Nginx)을 두어야 합니다. 직접적인 통신은 어렵습니다.

결론 및 추천

React + Spring Boot 조합에서 백엔드 API 설계 방법론으로 가장 보편적이고 권장되는 것은 RESTful API 입니다.

  • 대부분의 웹 서비스: RESTful API는 개발 생산성, 쉬운 이해, 광범위한 지원 도구 및 커뮤니티 측면에서 탁월합니다. Spring Boot와의 통합도 매우 강력합니다.
  • 복잡한 데이터 요구사항 또는 유연성이 중요한 경우: GraphQL을 고려해볼 수 있습니다. 특히 프론트엔드 개발자들이 필요한 데이터를 직접 정의하고 싶어 할 때 유용합니다. 하지만 백엔드에서의 구현 복잡성과 캐싱 문제를 고려해야 합니다.
  • 백엔드 마이크로서비스 간 고성능 통신 또는 스트리밍이 필요한 경우 (그리고 React에서 직접 호출할 필요가 없는 경우): gRPC는 탁월한 성능을 제공하지만, 웹 브라우저 지원의 한계 때문에 React와 직접 연동하기보다는 백엔드 내부 통신에 더 적합합니다. React와 연동하려면 gRPC-Web이라는 추가적인 설정이 필요합니다.

초기 프로젝트 진행 시에는 RESTful API로 시작하는 것을 강력히 추천합니다. 프로젝트가 성장하면서 특정 요구사항(예: 복잡한 데이터 페칭, 실시간성)이 발생하면 GraphQL이나 gRPC로 점진적으로 확장하거나, 각 방법론의 장점을 살려 혼합하여 사용하는 하이브리드 접근 방식을 고려할 수 있습니다. (예: 대부분은 REST로, 특정 복잡한 쿼리는 GraphQL로)

API 설계 시에는 항상 일관성, 명확성, 확장성, 보안을 염두에 두는 것이 중요합니다.

 

 

여기서 잠깐!
API URI설계시 사용자 한명의 정보조회와 사용자목록을 조회를 어떻게 구분하는것이 효율적인가?

권장되는 방법은

get /user/ 는 목록을 가져오는 것이고(물론 파라미터로 검색조건을 걸수있도록 하고),

get /user/{id}는 한명의 정보만 가져오는 uri 일관성을 제공함.

물론 사용자목록의 결과집합과 개인 한 명의 정보가 같을 수도 다를 수도 있도록 구성할 수 있겠지요

 

여기서 핵심은 컬렉션 리소스개별 리소스를 명확히 구분하는 것입니다.

  1. 컬렉션 리소스 (Collection Resource):
    • 여러 개의 항목(리소스)으로 구성된 집합을 의미합니다.
    • URI 예시: /users, /products, /articles
    • 이 컬렉션에 대한 GET 요청은 전체 목록을 조회하는 데 사용됩니다.
      • GET /users → 모든 사용자 목록 반환
    • 이 컬렉션에 대한 POST 요청은 새로운 항목을 생성하는 데 사용됩니다.
      • POST /users (요청 본문에 사용자 정보 포함) → 새로운 사용자 생성
  2. 개별 리소스 (Individual Resource):
    • 컬렉션 내의 특정 항목(하나의 리소스)을 의미합니다.
    • 일반적으로 URI에 해당 리소스의 **고유 식별자(ID)**를 포함합니다.
    • URI 예시: /users/{id}, /products/{id}, /articles/{id}
    • 이 개별 리소스에 대한 GET 요청은 특정 항목 하나를 조회하는 데 사용됩니다.
      • GET /users/123 → ID가 123인 사용자 정보 반환
    • 이 개별 리소스에 대한 PUT 또는 PATCH 요청은 특정 항목을 수정하는 데 사용됩니다.
      • PUT /users/123 (요청 본문에 수정된 사용자 정보 포함) → ID가 123인 사용자 정보 전체 업데이트
      • PATCH /users/123 (요청 본문에 부분적으로 수정된 사용자 정보 포함) → ID가 123인 사용자 정보 부분 업데이트
    • 이 개별 리소스에 대한 DELETE 요청은 특정 항목을 삭제하는 데 사용됩니다.
      • DELETE /users/123 → ID가 123인 사용자 삭제
사용자, 게시글..모두 동일한 형태로 적용가능합니다.
여기서 의문이 들수도 있습니다. 아니 들어야 합니다. 보안의 측면입니다.
uri명명규칙에 따른 예측가능성으로인한 보안의 문제가 발생하는것은 어떻게 할것인가?

 


URI 명명 규칙을 명확하고 일관성 있게 정하는 것은 RESTful API의 장점이지만, 말씀하신 대로 예측 가능성 때문에 보안에 대한 우려가 생길 수 있습니다. 공격자가 URI 패턴을 쉽게 추측하여 특정 리소스에 대한 접근을 시도하거나, 존재하지 않는 리소스를 무작위로 요청하여 서버에 부하를 줄 수도 있죠.
이러한 예측 가능성으로 인한 보안 문제 해결 방안은 다음과 같습니다.

1. 인증 및 권한 부여 (Authentication & Authorization) 강화
가장 근본적이고 중요한 해결책입니다. URI 예측 가능성 자체를 없애는 것보다, 예측된 URI에 접근하더라도 정당한 사용자만 접근할 수 있도록 강력하게 제어해야 합니다.
인증 (Authentication): 누가 API를 호출하는지 신원을 확인하는 과정입니다. JWT (JSON Web Token): React + Spring Boot 프로젝트에서 가장 흔히 사용되는 방식입니다. 사용자가 로그인하면 서버에서 JWT를 발급하고, 클라이언트는 이후 모든 요청에 이 토큰을 포함하여 보냅니다. 서버는 토큰의 유효성을 검증하여 사용자를 식별합니다. OAuth 2.0: 서드파티 애플리케이션에 대한 접근 권한을 부여할 때 사용됩니다. 권한 부여 (Authorization): 인증된 사용자가 특정 리소스나 기능에 접근할 권한이 있는지 확인하는 과정입니다. 역할 기반 접근 제어 (RBAC - Role-Based Access Control): 사용자에게 '관리자', '일반 사용자' 등 역할을 부여하고, 각 역할이 접근할 수 있는 API 엔드포인트나 데이터에 대한 권한을 정의합니다. Spring Security에서 @PreAuthorize 어노테이션 등을 활용하여 쉽게 구현할 수 있습니다. 속성 기반 접근 제어 (ABAC - Attribute-Based Access Control): 사용자, 리소스, 환경 속성 등 더 세밀한 조건을 기반으로 접근을 제어합니다. 복잡도가 높지만 더 유연합니다.
해결 원리: 공격자가 GET /users/123이라는 URI를 예측하더라도, 유효한 인증 토큰이 없거나, 해당 리소스에 접근할 권한이 없다면 401 Unauthorized 또는 403 Forbidden 응답을 받게 됩니다.

2. 고유 식별자 (ID)에 UUID 사용
일반적으로 리소스의 ID로 순차적인 정수(예: 1, 2, 3...)를 많이 사용합니다. 이 경우 공격자가 다음 ID를 쉽게 예측하여 무작위로 요청할 수 있습니다. 이를 방지하기 위해 **UUID (Universally Unique Identifier)**를 사용하는 것이 좋습니다.
UUID (Universally Unique Identifier): 32개의 16진수 문자로 구성된 고유 식별자입니다 (예: a1b2c3d4-e5f6-7890-1234-56789abcdef0). 예측이 거의 불가능하며, 병렬적으로 생성해도 충돌할 가능성이 극히 낮습니다. 적용 예시: GET /articles/a1b2c3d4-e5f6-7890-1234-56789abcdef0 DELETE /users/xyz123abc-def4-5678-90ab-cdef01234567
해결 원리: ID가 순차적이지 않고 예측 불가능한 문자열이 되므로, 공격자가 무작위로 ID를 추측하여 유효한 리소스에 접근할 가능성이 현저히 낮아집니다.

3. API 게이트웨이 및 속도 제한 (Rate Limiting)
API 요청의 빈도를 제어하여 무차별 대입 공격(Brute Force Attack)이나 서비스 거부(DoS) 공격을 완화할 수 있습니다.
API 게이트웨이: API 요청의 진입점으로, 요청을 실제 서비스로 라우팅하기 전에 인증, 로깅, 속도 제한, IP 화이트리스트/블랙리스트 관리 등 다양한 보안 정책을 적용할 수 있습니다. Spring Cloud Gateway 같은 기술을 활용할 수 있습니다. 속도 제한 (Rate Limiting): 특정 IP 주소, 사용자 또는 기간 동안 허용되는 API 요청의 수를 제한합니다. 예를 들어, 1분당 100회 이상의 요청이 오면 해당 요청을 차단하거나 지연시킬 수 있습니다. Spring Boot에서는 Bucket4j, Resilience4j 등의 라이브러리나 직접 인터셉터/필터를 구현하여 적용할 수 있습니다.
해결 원리: 공격자가 무작위로 URI를 추측하여 대량 요청을 보내더라도, 일정 횟수 이상 요청 시 차단되어 서버 부하를 줄이고 공격 시도를 무력화합니다.

4. 웹 방화벽 (WAF - Web Application Firewall) 도입
WAF는 웹 애플리케이션으로 들어오는 트래픽을 검사하고, SQL 인젝션, 크로스 사이트 스크립팅(XSS), 경로 탐색(Path Traversal) 등과 같은 일반적인 웹 공격으로부터 보호합니다. URI 패턴에 대한 비정상적인 접근 시도도 탐지하여 차단할 수 있습니다.
클라우드 서비스 (AWS WAF, Azure Application Gateway WAF 등) 또는 자체 구축형 솔루션을 사용할 수 있습니다.
해결 원리: 애플리케이션 레벨의 취약점 공격뿐만 아니라, 비정상적인 URI 접근 시도에 대한 1차적인 방어막 역할을 합니다.

5. API 로깅 및 모니터링
API 호출에 대한 상세한 로그를 기록하고, 이를 실시간으로 모니터링하여 비정상적인 접근 패턴이나 공격 시도를 조기에 감지해야 합니다.
로그 관리 시스템: ELK Stack (Elasticsearch, Logstash, Kibana) 또는 Splunk, Grafana Loki 등을 활용하여 API 접근 로그를 중앙 집중화하고 분석합니다. 이상 감지 시스템: 특정 URI에 대한 비정상적인 요청 빈도 증가, 특정 IP에서의 대량 요청, 비정상적인 HTTP 상태 코드 발생 등을 감지하여 알림을 보냅니다.
해결 원리: 공격이 발생하는 경우, 빠르게 인지하고 대응하여 피해를 최소화할 수 있습니다.


결론
URI 명명 규칙의 예측 가능성 자체를 완전히 제거하는 것은 RESTful API의 본질적인 이점(명확성, 가독성)을 훼손할 수 있습니다. 따라서, URI 예측 가능성 자체보다는 예측 가능한 URI에 대한 불법적인 접근을 강력하게 통제하는 보안 장치를 마련하는 것이 핵심 해결책입니다.


즉, 인증 및 권한 부여를 철저히 하고, 중요한 리소스 ID에는 UUID를 사용하며, API 게이트웨이와 속도 제한을 통해 비정상적인 접근 시도를 방어하고, 웹 방화벽 및 모니터링 시스템을 구축하는 것이 React + Spring Boot 프로젝트에서 예측 가능성으로 인한 보안 위험을 해결하는 가장 효과적인 방법입니다.

 

웹기술들이 발전할수록 사용자들은 시공을 초월하는 편리함과 효율성을 제공하지만 구축하는 입장에서는 아무 런 숨을 공간 없는 평야같은 인터넷상에 노출 되는것이므로 쉽게 먹잇감이 되지않도록 많은 방비를 해야하는 어려움이 있다. 하지만, 일반적으로 클라우드환경에서는 이러한 장치들이 이미 마련되어있으므로 잘 알아보고 적용하는것이 필수입니다.
반응형