GraphQL은 유연하고 요율적인 API 쿼리 언어이지만, 그만큼 보안적인 측면에서 신중한 설계가 요구된다. 단일 엔드포인트로 모든 요청을 처리한다는 특성상, 인증(Authentication), 권한 부여(Authorization), 쿼리 검증, 로깅 등 여러 계층의 보안 메커니즘이 필요하다.
GraphQL 보안 개요
GraphQL 보안은 다음과 같은 요소로 구성된다.
- 인증 (Authentication)
- 권한 부여 (Authorization)
- 쿼리 및 뮤테이션 검증
- 보안 이벤트 및 로깅
- 보안 업데이트 및 버전 관리
🔑 인증(Authentication)
GraphQL 쿼리를 실행하기 전, 클라이언트가 신뢰할 수 있는 사용자인지 검증하는 단계이다.
대표 인증 방식
- JWT (JSON Web Token)
👉 클라이언트가 로그인 후 받은 토큰을 요청 시 HTTP 헤더에 포함 - OAuth 2.0
👉 외부 인증 제공자를 통해 인증 (Google, Kakao 등)
예시: JWT 기반 인증 (Spring Security)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/graphql").authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
- 이 설정은 /graphql 요청에 대해 JWT 인증을 요구한다.
권한 부여(Authorization)
인증 이후, 요청한 사용자가 해당 데이터를 볼 수 있는 권한이 있는지 판단하는 단계이다.
리졸버에서 권한 체크 예시
@PreAuthorize("hasRole('ADMIN')")
public User getSensitiveUserInfo(Long id) {
return userService.getUserById(id);
}
- Spring Security와 함께 GraphQL을 사용할 경우, 리졸버에 직접 @PreAuthorize, @Secured 등의 애노테이션을 사용하여 권한을 제어할 수 있다.
쿼리 및 뮤테이션 검증
입력값이 유효한지, 요청 쿼리가 지나치게 크거나 복잡하지 않은지 검증한다.
검증 대상
- 입력값 유효성 검사
- 권한 검사
- 쿼리 구조 검증 (복잡도, 깊이 등)
보안 이벤트 및 로깅
GraphQL은 모든 요청이 단일 엔드포인트로 들어오므로, 정교한 로깅과 보안 이벤트 모니터링이 필요하다.
예시: GraphQL Instrumentation을 통한 요청 로깅
public class LoggingInstrumentation extends SimpleInstrumentation {
@Override
public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {
log.info("GraphQL Request: {}", parameters.getQuery());
return super.beginExecution(parameters);
}
}
보안 업데이트 및 버전 관리
GraphQL 스키마나 리졸버는 종종 변경된다. 보안적으로는 다음 내용을 주의해야 한다.
- 변경된 필드에 대한 권한 검증 추가
- 민감한 필드 제거 후 API 버전 관리 적용
- 의도치 않은 정보 노출 방지
인증과 권한 적용 방법
GraphQL은 단일 엔드포인트(/graphql)를 통해 다양한 쿼리와 뮤테이션을 처리한다. 이 특성 때문에 인증과 권한 관리에 신경 써야 할 포인트가 몇 가지 있다.
1. Spring Security로 인증 처리
가장 기본적인 방식으로, GraphQL 요청이 들어오는 /graphql 엔드포인트 전체에 인증을 요구한다.
📌 예시: Spring Security 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/graphql").authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.oauth2ResourceServer().jwt(); // 또는 JWT 필터 사용 가능
return http.build();
}
}
- 인증이 안 된 요청은 모두 401 Unauthorized 응답
- 로그인 또는 토큰이 필요한 API 서버에서 사용
2. /graphql 전체 허용 + 특정 Query/Muation에만 인증 적용
이 경우, GraphQL 엔드포인트는 열려 있지만, 쿼리/뮤테이션 실행 시점에 인증 여부를 리졸버 단에서 검사한다.
📌 예시: 리졸버 메서드에 권한 제어 애노테이션 사용
@Component
public class UserResolver {
@PreAuthorize("isAuthenticated()")
public User getMyProfile() {
// 현재 로그인된 사용자 정보 조회
}
public List<Product> getPublicProducts() {
// 인증 없이 호출 가능
}
}
- 인증이 필요한 쿼리와 아닌 쿼리를 분리 가능
- 공용 쿼리와 개인 쿼리를 모두 제공하는 서비스에 적합
3. Directive를 활용한 인증 및 권한 제어
GraphQL의 Directive는 쿼리 또는 스키마 수준에서 조건을 설정할 수 있도록 하는 강력한 도구이다. 이를 인증/권한 시스템과 연동하면 스키마 기반 권한 제어가 가능하다.
1️⃣ 인증 디렉티브 정의
directive @auth(role: Role = USER) on FIELD_DEFINITION
enum Role {
USER
ADMIN
}
2️⃣ 스키마에 디렉티브 적용
type Query {
myOrders: [Order] @auth(role: USER)
allUsers: [User] @auth(role: ADMIN)
}
3️⃣ Java에서 디렉티브 처리 구현
@Component
public class AuthDirectiveWiring implements SchemaDirectiveWiring {
@Override
public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> env) {
DataFetcher<?> originalFetcher = env.getFieldDataFetcher();
String requiredRole = env.getDirective().getArgument("role").getValue().toString();
DataFetcher<?> authFetcher = dataEnv -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !auth.isAuthenticated()) {
throw new AccessDeniedException("Unauthorized");
}
if (!auth.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_" + requiredRole))) {
throw new AccessDeniedException("Forbidden");
}
return originalFetcher.get(dataEnv);
};
env.setFieldDataFetcher(authFetcher);
return env.getElement();
}
}
- 인증 및 권한 요구사항이 스키마에 명시됨
- 보안 정책이 코드가 아닌 스키마 중심으로 관리 가능
- 쿼리와 뮤테이션 모두에 적용 가능
쿼리 보호 전략 (실전 팁)
GraphQL은 매우 유연한 쿼리 언어이지만, 이 유연성이 곧 보안 위협이 될 수 있다. 예를 들어 악의적인 사용자가 과도하게 복잡한 쿼리를 보내 서버 리소스를 고갈시키거나, 깊게 중첩된 요청으로 시스템을 마비시킬 수 있다.
세 가지 주요 전략
- 쿼리 실행 Timeout 설정
- 쿼리 복잡도 / 깊이 / 길이 제한
- Whitelist(허용 목록) 기반 쿼리 검증
쿼리 실행 시간 제한 (Timeout 적용)
왜 필요한가?
GraphQL 쿼리는 매우 깊게 중첩되거나, 의도적으로 리졸버를 오래 대기시키는 방식으로 공격할 수 있다. 이러한 요청이 반복되면 서버 리소스를 점유해 서비스 장애(DOS)를 유발할 수 있다.
구현 방법 1: Instrumentation을 활용한 Timeout 적용
GraphQL Java에서는 Instrumentation을 활용해 쿼리 실행 시간을 제한할 수 있다.
public class TimeoutInstrumentation extends SimpleInstrumentation {
private final ExecutorService executor = Executors.newCachedThreadPool();
private final long timeoutMillis;
public TimeoutInstrumentation(long timeoutMillis) {
this.timeoutMillis = timeoutMillis;
}
@Override
public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {
Future<ExecutionResult> future = executor.submit(() ->
parameters.getExecutionInput().getGraphQL().execute(parameters.getExecutionInput())
);
try {
return new SimpleInstrumentationContext<>() {
@Override
public void onCompleted(ExecutionResult result, Throwable t) {
future.cancel(true);
}
};
} catch (Exception e) {
throw new RuntimeException("GraphQL Timeout", e);
}
}
}
📌 적용 방법
GraphQL.newGraphQL(schema)
.instrumentation(new TimeoutInstrumentation(2000)) // 2초 제한
.build();
- 요청이 2초 이상 걸리면 자동으로 중단되고, 에러 응답이 반환된다.
쿼리 제한: 복잡도, 깊이, 길이 제한
왜 필요한가?
GraphQL은 클라이언트가 쿼리 구조를 완전히 정의할 수 있기 때문에, 무분별한 요청을 막을 수단이 필요하다.
제한 방식
제한 항목 | 설명 |
Complexity | 쿼리의 연산량을 기준으로 제한 |
Depth | 중첩 레벨을 기준으로 제한 |
Length | 전체 쿼리 문자열의 길이를 기준으로 제한 |
Query Complexity 계산 예시
query {
user(id: 1) {
id
name
orders {
id
total
items {
id
name
}
}
}
}
- user: 1
- orders: 5
- items: 3
- 총 Complexity: 1 + 5 + (3 x N) <- N은 아이템 수
GraphQL Java 설정 예시
GraphQL graphQL = GraphQL
.newGraphQL(schema)
.instrumentation(
new ChainedInstrumentation(List.of(
new MaxQueryComplexityInstrumentation(100),
new MaxQueryDepthInstrumentation(10)
))
)
.build();
- MaxQueryDepthInstrumentation: 중첩 깊이를 제한
쿼리 Whitelist 적용
왜 필요한가?
GraphQL은 단일 엔드포인트(/graphql)에서 모든 쿼리를 처리하기 때문에, 미리 승인된 쿼리 외에는 실행되지 않도록 제한하는 전략이 필요할 수 있다.
주로 금융, 정부, 의료 등 보안이 중요한 API에서 활용된다.
구현 방법 예시
1️⃣ 허용할 쿼리를 식별자(해시) 또는 ID로 등록
{
"queryId": "getUserById",
"variables": { "id": "123" }
}
2️⃣ 서버에서 허용된 쿼리만 매핑
Map<String, String> whitelist = Map.of(
"getUserById", "query getUser($id: ID!) { user(id: $id) { name } }"
);
3️⃣ 요청 시 전달된 ID 기반 쿼리를 대체
String query = whitelist.get(request.getQueryId());
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(query)
.variables(request.getVariables())
.build();
'기술(Tech) > Network & System' 카테고리의 다른 글
[GraphQL] GraphQL 쿼리(Query)와 뮤테이션(Mutation) 작성법 (0) | 2025.03.17 |
---|---|
[GraphQL] GraphQL 쿼리(Query) vs 뮤테이션(Mutation) (0) | 2025.03.16 |
[GraphQL] GraphQL 쿼리(Query) 언어 (0) | 2025.03.16 |
[GraphQL] GraphQL의 스키마와 타입 시스템 (0) | 2025.03.16 |
[GraphQL] GraphQL 개요 (0) | 2025.03.15 |
GraphQL은 유연하고 요율적인 API 쿼리 언어이지만, 그만큼 보안적인 측면에서 신중한 설계가 요구된다. 단일 엔드포인트로 모든 요청을 처리한다는 특성상, 인증(Authentication), 권한 부여(Authorization), 쿼리 검증, 로깅 등 여러 계층의 보안 메커니즘이 필요하다.
GraphQL 보안 개요
GraphQL 보안은 다음과 같은 요소로 구성된다.
- 인증 (Authentication)
- 권한 부여 (Authorization)
- 쿼리 및 뮤테이션 검증
- 보안 이벤트 및 로깅
- 보안 업데이트 및 버전 관리
🔑 인증(Authentication)
GraphQL 쿼리를 실행하기 전, 클라이언트가 신뢰할 수 있는 사용자인지 검증하는 단계이다.
대표 인증 방식
- JWT (JSON Web Token)
👉 클라이언트가 로그인 후 받은 토큰을 요청 시 HTTP 헤더에 포함 - OAuth 2.0
👉 외부 인증 제공자를 통해 인증 (Google, Kakao 등)
예시: JWT 기반 인증 (Spring Security)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/graphql").authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
- 이 설정은 /graphql 요청에 대해 JWT 인증을 요구한다.
권한 부여(Authorization)
인증 이후, 요청한 사용자가 해당 데이터를 볼 수 있는 권한이 있는지 판단하는 단계이다.
리졸버에서 권한 체크 예시
@PreAuthorize("hasRole('ADMIN')")
public User getSensitiveUserInfo(Long id) {
return userService.getUserById(id);
}
- Spring Security와 함께 GraphQL을 사용할 경우, 리졸버에 직접 @PreAuthorize, @Secured 등의 애노테이션을 사용하여 권한을 제어할 수 있다.
쿼리 및 뮤테이션 검증
입력값이 유효한지, 요청 쿼리가 지나치게 크거나 복잡하지 않은지 검증한다.
검증 대상
- 입력값 유효성 검사
- 권한 검사
- 쿼리 구조 검증 (복잡도, 깊이 등)
보안 이벤트 및 로깅
GraphQL은 모든 요청이 단일 엔드포인트로 들어오므로, 정교한 로깅과 보안 이벤트 모니터링이 필요하다.
예시: GraphQL Instrumentation을 통한 요청 로깅
public class LoggingInstrumentation extends SimpleInstrumentation {
@Override
public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {
log.info("GraphQL Request: {}", parameters.getQuery());
return super.beginExecution(parameters);
}
}
보안 업데이트 및 버전 관리
GraphQL 스키마나 리졸버는 종종 변경된다. 보안적으로는 다음 내용을 주의해야 한다.
- 변경된 필드에 대한 권한 검증 추가
- 민감한 필드 제거 후 API 버전 관리 적용
- 의도치 않은 정보 노출 방지
인증과 권한 적용 방법
GraphQL은 단일 엔드포인트(/graphql)를 통해 다양한 쿼리와 뮤테이션을 처리한다. 이 특성 때문에 인증과 권한 관리에 신경 써야 할 포인트가 몇 가지 있다.
1. Spring Security로 인증 처리
가장 기본적인 방식으로, GraphQL 요청이 들어오는 /graphql 엔드포인트 전체에 인증을 요구한다.
📌 예시: Spring Security 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/graphql").authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.oauth2ResourceServer().jwt(); // 또는 JWT 필터 사용 가능
return http.build();
}
}
- 인증이 안 된 요청은 모두 401 Unauthorized 응답
- 로그인 또는 토큰이 필요한 API 서버에서 사용
2. /graphql 전체 허용 + 특정 Query/Muation에만 인증 적용
이 경우, GraphQL 엔드포인트는 열려 있지만, 쿼리/뮤테이션 실행 시점에 인증 여부를 리졸버 단에서 검사한다.
📌 예시: 리졸버 메서드에 권한 제어 애노테이션 사용
@Component
public class UserResolver {
@PreAuthorize("isAuthenticated()")
public User getMyProfile() {
// 현재 로그인된 사용자 정보 조회
}
public List<Product> getPublicProducts() {
// 인증 없이 호출 가능
}
}
- 인증이 필요한 쿼리와 아닌 쿼리를 분리 가능
- 공용 쿼리와 개인 쿼리를 모두 제공하는 서비스에 적합
3. Directive를 활용한 인증 및 권한 제어
GraphQL의 Directive는 쿼리 또는 스키마 수준에서 조건을 설정할 수 있도록 하는 강력한 도구이다. 이를 인증/권한 시스템과 연동하면 스키마 기반 권한 제어가 가능하다.
1️⃣ 인증 디렉티브 정의
directive @auth(role: Role = USER) on FIELD_DEFINITION
enum Role {
USER
ADMIN
}
2️⃣ 스키마에 디렉티브 적용
type Query {
myOrders: [Order] @auth(role: USER)
allUsers: [User] @auth(role: ADMIN)
}
3️⃣ Java에서 디렉티브 처리 구현
@Component
public class AuthDirectiveWiring implements SchemaDirectiveWiring {
@Override
public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> env) {
DataFetcher<?> originalFetcher = env.getFieldDataFetcher();
String requiredRole = env.getDirective().getArgument("role").getValue().toString();
DataFetcher<?> authFetcher = dataEnv -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !auth.isAuthenticated()) {
throw new AccessDeniedException("Unauthorized");
}
if (!auth.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_" + requiredRole))) {
throw new AccessDeniedException("Forbidden");
}
return originalFetcher.get(dataEnv);
};
env.setFieldDataFetcher(authFetcher);
return env.getElement();
}
}
- 인증 및 권한 요구사항이 스키마에 명시됨
- 보안 정책이 코드가 아닌 스키마 중심으로 관리 가능
- 쿼리와 뮤테이션 모두에 적용 가능
쿼리 보호 전략 (실전 팁)
GraphQL은 매우 유연한 쿼리 언어이지만, 이 유연성이 곧 보안 위협이 될 수 있다. 예를 들어 악의적인 사용자가 과도하게 복잡한 쿼리를 보내 서버 리소스를 고갈시키거나, 깊게 중첩된 요청으로 시스템을 마비시킬 수 있다.
세 가지 주요 전략
- 쿼리 실행 Timeout 설정
- 쿼리 복잡도 / 깊이 / 길이 제한
- Whitelist(허용 목록) 기반 쿼리 검증
쿼리 실행 시간 제한 (Timeout 적용)
왜 필요한가?
GraphQL 쿼리는 매우 깊게 중첩되거나, 의도적으로 리졸버를 오래 대기시키는 방식으로 공격할 수 있다. 이러한 요청이 반복되면 서버 리소스를 점유해 서비스 장애(DOS)를 유발할 수 있다.
구현 방법 1: Instrumentation을 활용한 Timeout 적용
GraphQL Java에서는 Instrumentation을 활용해 쿼리 실행 시간을 제한할 수 있다.
public class TimeoutInstrumentation extends SimpleInstrumentation {
private final ExecutorService executor = Executors.newCachedThreadPool();
private final long timeoutMillis;
public TimeoutInstrumentation(long timeoutMillis) {
this.timeoutMillis = timeoutMillis;
}
@Override
public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {
Future<ExecutionResult> future = executor.submit(() ->
parameters.getExecutionInput().getGraphQL().execute(parameters.getExecutionInput())
);
try {
return new SimpleInstrumentationContext<>() {
@Override
public void onCompleted(ExecutionResult result, Throwable t) {
future.cancel(true);
}
};
} catch (Exception e) {
throw new RuntimeException("GraphQL Timeout", e);
}
}
}
📌 적용 방법
GraphQL.newGraphQL(schema)
.instrumentation(new TimeoutInstrumentation(2000)) // 2초 제한
.build();
- 요청이 2초 이상 걸리면 자동으로 중단되고, 에러 응답이 반환된다.
쿼리 제한: 복잡도, 깊이, 길이 제한
왜 필요한가?
GraphQL은 클라이언트가 쿼리 구조를 완전히 정의할 수 있기 때문에, 무분별한 요청을 막을 수단이 필요하다.
제한 방식
제한 항목 | 설명 |
Complexity | 쿼리의 연산량을 기준으로 제한 |
Depth | 중첩 레벨을 기준으로 제한 |
Length | 전체 쿼리 문자열의 길이를 기준으로 제한 |
Query Complexity 계산 예시
query {
user(id: 1) {
id
name
orders {
id
total
items {
id
name
}
}
}
}
- user: 1
- orders: 5
- items: 3
- 총 Complexity: 1 + 5 + (3 x N) <- N은 아이템 수
GraphQL Java 설정 예시
GraphQL graphQL = GraphQL
.newGraphQL(schema)
.instrumentation(
new ChainedInstrumentation(List.of(
new MaxQueryComplexityInstrumentation(100),
new MaxQueryDepthInstrumentation(10)
))
)
.build();
- MaxQueryDepthInstrumentation: 중첩 깊이를 제한
쿼리 Whitelist 적용
왜 필요한가?
GraphQL은 단일 엔드포인트(/graphql)에서 모든 쿼리를 처리하기 때문에, 미리 승인된 쿼리 외에는 실행되지 않도록 제한하는 전략이 필요할 수 있다.
주로 금융, 정부, 의료 등 보안이 중요한 API에서 활용된다.
구현 방법 예시
1️⃣ 허용할 쿼리를 식별자(해시) 또는 ID로 등록
{
"queryId": "getUserById",
"variables": { "id": "123" }
}
2️⃣ 서버에서 허용된 쿼리만 매핑
Map<String, String> whitelist = Map.of(
"getUserById", "query getUser($id: ID!) { user(id: $id) { name } }"
);
3️⃣ 요청 시 전달된 ID 기반 쿼리를 대체
String query = whitelist.get(request.getQueryId());
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(query)
.variables(request.getVariables())
.build();
'기술(Tech) > Network & System' 카테고리의 다른 글
[GraphQL] GraphQL 쿼리(Query)와 뮤테이션(Mutation) 작성법 (0) | 2025.03.17 |
---|---|
[GraphQL] GraphQL 쿼리(Query) vs 뮤테이션(Mutation) (0) | 2025.03.16 |
[GraphQL] GraphQL 쿼리(Query) 언어 (0) | 2025.03.16 |
[GraphQL] GraphQL의 스키마와 타입 시스템 (0) | 2025.03.16 |
[GraphQL] GraphQL 개요 (0) | 2025.03.15 |