GraphQL, como un mecanismo universal de consultas, abre al cliente el acceso a los datos a través de un único punto de entrada — el GraphQL API. Esto simplifica la arquitectura, pero complica la seguridad:
- Punto único de fallo. Si ese punto se compromete, un atacante podría acceder a todo el sistema.
- Flexibilidad de las consultas. El usuario puede pedir demasiados datos (o anidamientos demasiado profundos) en una sola consulta.
- Recursos sin límites. GraphQL permite a los clientes elegir cuántos datos necesitan, lo que puede llevar a abusos de los recursos del servidor.
Por tanto, necesitamos proteger nuestros datos contra accesos no autorizados, prevenir la sobrecarga del servidor y asegurar el cumplimiento de las políticas de acceso.
Amenazas potenciales en GraphQL
- Denegación de servicio (DoS): por ejemplo, un ataque a la disponibilidad donde el cliente solicita datos enormemente anidados (o consultas cíclicas).
- Violación de la privacidad de los datos: si los usuarios pueden acceder a datos a los que no tienen permiso.
- Mutaciones no autorizadas: un atacante puede intentar modificar datos sin autorización.
- Inyecciones a través de consultas GraphQL: aunque GraphQL por naturaleza protege contra SQL injections, las consultas dinámicas pueden arruinarlo todo.
Aspectos principales de la seguridad en GraphQL
- Autenticación. ¿Quién eres? Es el proceso de verificar la identidad del usuario.
- Autorización. ¿Qué puedes hacer? Es la comprobación de permisos para ejecutar acciones concretas.
- Restricciones de consultas. Son herramientas que protegen al servidor de la sobrecarga.
- Protección del esquema. Eliminamos filtraciones de información sobre la arquitectura del API.
Control de acceso en GraphQL
El enfoque principal es usar el contexto (context) de la petición. El contexto es un objeto que se pasa a cada consulta. Puede contener información sobre el usuario actual, su rol, el token de acceso y otros parámetros.
Ejemplo de configuración del contexto para GraphQL:
@Bean
public GraphQL graphQL(GraphQLSchema schema) {
return GraphQL.newGraphQL(schema)
.defaultDataFetcherExceptionHandler(new CustomExceptionHandler())
.instrumentation(new ContextSettingInstrumentation()) // Contexto para las peticiones
.build();
}
public class ContextSettingInstrumentation extends SimpleInstrumentation {
@Override
public InstrumentationContext<ExecutionInput> beginExecution(InstrumentationExecutionParameters parameters) {
ExecutionInput executionInput = parameters.getExecutionInput();
Map<String, Object> context = new HashMap<>();
context.put("currentUser", fetchCurrentUserFromSecurityContext()); // Añadimos el usuario
executionInput.transform(builder -> builder.context(context));
return super.beginExecution(parameters);
}
}
Autenticación con JWT
Usar JWT (JSON Web Token) permite verificar si el usuario realmente tiene derecho de acceso.
1. Configuramos SecurityContext en Spring Security:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
2. Comprobamos el usuario en el Data Fetcher de GraphQL:
public DataFetcher<String> getSecretInfo() {
return environment -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !isUserAuthenticated(authentication)) {
throw new AccessDeniedException("Unauthorized");
}
return "¡Aquí tu contenido secreto!";
};
}
Protección de datos y del esquema
Limitación de la profundidad de las consultas
Una de las formas más sencillas de atacar un GraphQL API es crear consultas con alto nivel de anidamiento, por ejemplo:
query {
user {
friends {
friends {
friends {
friends {
name
}
}
}
}
}
}
Para prevenir esto usamos la librería graphql-java-servlet, que permite configurar límites de profundidad de consulta.
Ejemplo de añadir la limitación:
GraphQLSchema schema = SchemaGenerator.newBuilder()
.query(queryType)
.additionalDirective(graphql.schema.idl.SchemaPrinter.Options.defaultOptions().maxQueryDepth(10))
.build();
Limitación de la complejidad de las consultas
La complejidad de una consulta es la suma de los "pesos" de todos los campos solicitados. Cuantos más datos anidados se pidan, mayor es la complejidad. El peso de los campos se puede definir manualmente:
public class CustomFieldComplexityCalculator implements FieldComplexityCalculator {
@Override
public int calculate(FieldComplexityEnvironment environment, int childComplexity) {
if ("user".equals(environment.getField().getName())) {
return 5 + childComplexity; // Peso del campo "user" - 5
}
return 1 + childComplexity;
}
}
Después de esto configuramos GraphQL:
GraphQL.newGraphQL(schema)
.instrumentation(new MaxQueryComplexityInstrumentation(50)) // Límite de complejidad de consultas
.build();
Aplicación de autorización a nivel de esquema
Usa anotaciones para limitar el acceso a campos concretos:
@GraphQLField
@PreAuthorize("hasRole('ADMIN')")
public String getAdminData() {
return "¡Solo para administradores!";
}
Spring Security comprobará automáticamente el rol del usuario antes de ejecutar el método.
Práctica: configurar la seguridad del GraphQL API
Paso 1: Configura Spring Security.
Añade las dependencias en pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
Paso 2: Configura JWT en Spring Boot.
Añade la configuración en application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://your-issuer.com
Paso 3: Añade la comprobación de acceso en los Data Fetchers.
Integra la comprobación de permisos de los usuarios en los manejadores de consultas GraphQL:
public DataFetcher<String> getSecureInfo() {
return environment -> {
if (!environment.getContext().get("isAuthenticated")) {
throw new AccessDeniedException("Access Denied");
}
return "Datos secretos para usuarios autenticados";
};
}
Paso 4: Limita la profundidad y complejidad de las consultas.
Añade en la configuración de GraphQL límites a la profundidad y complejidad:
GraphQL.newGraphQL(schema)
.instrumentation(new MaxQueryDepthInstrumentation(10))
.instrumentation(new MaxQueryComplexityInstrumentation(50))
.build();
Conclusión
Si la seguridad fuera una tarta, ¡acabamos de añadir suficientes ingredientes para seguir la receta! Ahora tu GraphQL API está protegido contra peticiones excesivas, accesos no autorizados y posibles ataques. Pero recuerda que la seguridad no es un proceso de "hacerlo una vez y olvidarlo". Revisa regularmente tus configuraciones, actualiza las librerías y prueba vulnerabilidades.
¡Pasemos al siguiente paso!
GO TO FULL VERSION