GraphQL với Spring Boot
Bảo mật và quan sát

Bảo mật và quan sát

Lộ trình bảo vệ GraphQL API và xây dựng observability cho ứng dụng Spring Boot.

Nhóm Bảo mật và quan sát là entry point để đưa một GraphQL API từ trạng thái “gọi được” đến trạng thái có thể bảo vệ và điều tra khi vận hành. GraphQL thường gom nhiều query, mutation và subscription vào cùng một endpoint, nên không thể chỉ bảo vệ bằng cách kiểm tra URL. Cần phối hợp identity, quyền, chi phí query, chính sách transport và tín hiệu vận hành.

Phạm vi và giả định

Các SDL, query, JSON, YAML, Java fragment và sơ đồ chữ trong nhóm này đều là ví dụ minh họa tĩnh. Repository hiện là docs-only: không có backend Spring Boot, Spring Security, identity provider, endpoint /graphql, Actuator, WebSocket, CORS/CSRF configuration hay log/trace/metric runtime nào được khẳng định là đã cấu hình. Tên route và property chỉ là ví dụ; hãy đối chiếu version Spring Boot/Spring for GraphQL, web stack và security stack của project thật trước khi áp dụng.

Mục lục

Nhóm này giải quyết điều gì?

Một request GraphQL cần được nhìn qua nhiều boundary. Header hoặc cookie đi qua transport; GraphQL parse và validate document; resolver gọi service; service truy cập data source hoặc event source. Mỗi boundary có trách nhiệm bảo vệ và tín hiệu riêng.

Credential và transport policyAuthentication: principal và context server-sideAuthorization: operation, field, resource và tenantQuery guardrail: depth, complexity, page size và timeoutIntrospection, CORS và CSRF: schema/browser exposureResolverservicerepository hoặc event sourceLogs, traces, metrics và health: tín hiệu vận hành
Credential và transport policyAuthentication: principal và context server-sideAuthorization: operation, field, resource và tenantQuery guardrail: depth, complexity, page size và timeoutIntrospection, CORS và CSRF: schema/browser exposureResolverservicerepository hoặc event sourceLogs, traces, metrics và health: tín hiệu vận hành
Security và tín hiệu vận hành

Đây là mental model, không phải kiến trúc đã được bật trong repository. Một query hợp lệ về schema vẫn có thể quá đắt. Một request đã authenticated vẫn có thể không được phép đọc một field. Một dashboard xanh cũng không chứng minh authorization đang đúng. Nhóm này giúp đặt từng câu hỏi vào đúng lớp thay vì dùng một cơ chế để thay thế tất cả cơ chế còn lại.

Điều kiện tiên quyết

Bạn nên có các nền tảng sau trước khi đi qua lộ trình này:

Không cần backend chạy để đọc các khái niệm. Muốn xác nhận endpoint, filter, security context, resolver, WebSocket hoặc Actuator thật sự hoạt động, bạn cần một project Spring Boot tương ứng và test ở runtime.

Mục tiêu học tập

Sau khi hoàn thành nhóm này, bạn có thể:

  • Mô tả threat model của GraphQL endpoint: caller không xác thực, user đã xác thực nhưng thiếu quyền, query độc hại, browser cross-origin và rò rỉ telemetry.
  • Phân biệt authentication với authorization, rồi truyền principal/tenant đã xác minh qua context mà không tin tenantId, userId hoặc permission flag do client gửi.
  • Chọn điểm áp dụng authorization ở operation, field, service, repository hoặc event source; hiểu vì sao chỉ ẩn field trên GraphiQL không phải là bảo vệ.
  • Đặt depth limit, complexity/cost budget, page-size cap, timeout và rate limit theo rủi ro tài nguyên thay vì chỉ nhìn số field trong query.
  • Quyết định chính sách introspection, CORS và CSRF theo môi trường, browser flow, cookie/bearer strategy và mức độ công khai của schema.
  • Thiết kế log và trace có operation name/query hash, correlation ID, field path và latency mà không ghi token, variables nhạy cảm, PII hoặc metric label cardinality cao.
  • Chọn metric và health signal giúp phát hiện latency, error rate, query bị từ chối, dependency failure và subscription/resource leak; đồng thời biết chúng không thay thế protection.
  • Viết checklist và test negative case để kiểm tra fail closed, tenant isolation, query budget, browser policy và redaction trước production.

Threat model và mục tiêu bảo vệ

Threat model

GraphQL làm cho một operation có thể chọn nhiều field, lồng nhiều tầng và gọi nhiều data source phía sau một route chung. Vì vậy, threat model phải xét cả danh tính, hình dạng query, transport và dữ liệu telemetry.

Actor hoặc điều kiệnRủi ro cần tính đếnLớp kiểm soát cần có
Caller không có credential, token sai, hết hạn hoặc bị thu hồiĐọc hoặc ghi dữ liệu khi chưa xác định được principalAuthentication ở boundary phù hợp; fail closed trước resolver khi không xác minh được identity
User hợp lệ nhưng thiếu quyền hoặc thuộc tenant khácIDOR, lộ field nhạy cảm, lộ existence qua null, count, timing hoặc errorAuthorization theo operation/field/resource và service/repository; context do server tạo
Client độc hại hoặc client vô tình tạo query nặngDoS tài nguyên qua depth, fragments, aliases, list lớn, relation sâu, N+1 hoặc field đắtComplexity/depth budget, page-size cap, timeout, rate limit và policy từ chối trước công việc tốn kém
Browser gửi cookie từ origin không mong muốnCSRF cho thao tác có side effect hoặc request cross-origin không được kiểm soátCORS allowlist, CSRF protection, SameSite/Origin policy và strategy credential rõ ràng
Caller dò schema hoặc công cụ debug bị public ngoài dự kiếnLộ vocabulary/type/field giúp reconnaissance, dù bản thân schema không phải secretChính sách introspection theo môi trường, allowlist/persisted operation khi phù hợp; không dùng việc tắt introspection làm authorization
Người đọc log/trace hoặc hệ thống telemetry bị truy cập saiLộ bearer token, cookie, variables, PII, query text hoặc tenant metadataAllowlist field log, redaction, sampling, retention và metric label cardinality thấp
Downstream chậm, stream bị hủy hoặc instance restartKhông có bằng chứng để phân biệt lỗi auth, lỗi query, lỗi data source và leak resourceCorrelation ID, trace, error code/path, latency metric, health/readiness và cleanup signal

Threat model này là điểm bắt đầu cho một API GraphQL thông thường. Domain có thanh toán, dữ liệu y tế, multi-tenant nghiêm ngặt hoặc compliance sẽ cần thêm asset, actor, retention và audit requirement riêng. Nói ngắn gọn: hãy giả định mọi input client đều không đáng tin, kể cả khi nó có kiểu GraphQL hợp lệ.

Mục tiêu bảo vệ

Lộ trình tập trung vào năm mục tiêu theo thứ tự phụ thuộc:

  1. Authentication: biết request hoặc connection thuộc principal nào, credential có hợp lệ và còn hiệu lực không.
  2. Authorization: quyết định principal đó có được gọi operation, đọc field, dùng resource, tenant hoặc event hiện tại không.
  3. Query cost và depth: giới hạn công việc mà một operation có thể yêu cầu trước khi resolver/data source bị tiêu tốn tài nguyên.
  4. Introspection, CORS và CSRF: kiểm soát mức schema được khám phá và cách browser gửi credential tới HTTP endpoint. Các policy này bổ sung cho auth, không thay auth.
  5. Logging, tracing, metrics và health: tạo tín hiệu đủ an toàn để biết policy có bị từ chối, request có chậm, dependency có lỗi và resource có được cleanup không.

Ba mục tiêu đầu là protection trực tiếp trên execution path. Mục tiêu thứ tư bảo vệ bề mặt exposure/transport. Mục tiêu cuối là observability; nó giúp phát hiện và điều tra, nhưng không tự chặn một query hay cấp quyền cho một user.

Protection và observability

Protection là các lớp chặn hoặc giảm rủi ro

Protection trả lời câu hỏi: request nào được đi tiếp, với phạm vi nào và ngân sách bao nhiêu? Một policy protection thường tạo ra hành động như reject, deny, timeout hoặc giới hạn dữ liệu.

Lớp protectionVí dụ quyết địnhKhông nên suy ra
AuthenticationToken hợp lệ mới tạo principal; thiếu hoặc sai credential thì dừngCó principal không có nghĩa là được đọc mọi resource
AuthorizationUser chỉ đọc field/resource/tenant đã được phép; service kiểm tra lại invariantẨn field khỏi GraphiQL hoặc đổi tên resolver không tạo quyền
Query guardrailTừ chối query vượt depth/complexity/page-size/timeout budgetQuery ngắn về văn bản vẫn có thể tạo nhiều nested fetch hoặc N+1
Schema và browser exposureChọn introspection policy, origin allowlist, CSRF policy và credential modeCORS không phải authentication; tắt introspection không phải authorization
Data/resource policyGiới hạn tenant, relation, event source, downstream và retryGraphQL type hoặc ID tự bảo vệ data source

Protection nên fail closed khi thiếu context hoặc không thể đánh giá quyền. Error public cần an toàn và ổn định; log nội bộ mới giữ chi tiết để điều tra. Đặt rule ở service/repository khi rule phải áp dụng cho cả GraphQL, REST, job hoặc consumer khác, thay vì chỉ đặt trong một resolver.

Observability là các tín hiệu để điều tra

Observability trả lời câu hỏi: điều gì đã xảy ra, ở đâu, với chi phí bao nhiêu và có lặp lại không? Nó thường gồm ba tín hiệu chính cùng health signal:

  • Logging: ghi event đã chuẩn hóa như operationName, query hash, error code/path, principal đã làm mờ, correlation ID và duration. Log không được chứa token, cookie, raw variables hoặc stack trace trong response.
  • Tracing: nối span từ gateway/interceptor qua GraphQL execution, resolver, service và downstream. Trace giúp tìm field hoặc dependency chậm; nó không tự áp authorization hay query budget.
  • Metrics: đếm latency, error rate, auth failure, query bị reject, active connection/operation, queue/broker lag và cleanup. Dùng label có cardinality thấp; không đưa raw ID, email hoặc query text động vào label.
  • Health: biểu diễn liveness/readiness và trạng thái dependency theo policy. Health endpoint có thể xanh trong khi một operation cụ thể bị FORBIDDEN hoặc một query shape hiếm đang chậm.

Protection và observability phải được thiết kế cùng nhau. Nếu chỉ có protection, team có thể chặn đúng nhưng không biết vì sao traffic bị từ chối. Nếu chỉ có observability, team có thể thấy tấn công nhưng vẫn để request đi tới database. Telemetry cũng là một asset cần bảo vệ và phải tuân theo redaction, retention và access policy.

Các cơ chế này không thay thế nhau

Tắt introspection không thay cho authorization. CORS không thay cho authentication hoặc CSRF protection. Ghi log một lỗi 401 không có nghĩa request đã bị chặn đúng ở mọi transport. Metric latency thấp cũng không chứng minh tenant isolation hoặc field authorization đúng. Hãy kiểm tra từng control bằng test và bằng tín hiệu tương ứng.

Thứ tự đọc và triển khai đề xuất

Thứ tự dưới đây đi từ danh tính đến quyền, từ quyền đến chi phí execution, rồi mới chốt schema/browser exposure và telemetry. Đây là thứ tự ưu tiên để học và rollout policy; không có nghĩa mọi cấu hình phải được bật trong một release duy nhất. TLS, secret management, dependency update và network baseline vẫn là điều kiện nền của production.

BướcBài con và lớp trách nhiệmCâu hỏi cần trả lời khi triển khai
1Authentication với JWT/OAuth2Credential được xác minh ở đâu? Principal, tenant, expiry/revocation và correlation metadata đi vào context HTTP hoặc WebSocket thế nào?
2Authorization theo field và resolverAi được gọi operation, đọc field/resource nào? Policy có được áp dụng lại ở service/repository/event source và không tin input client làm authority không?
3Giới hạn độ sâu và complexityMột query có thể tiêu tốn bao nhiêu depth, node, relation, alias, fragment hoặc database time? Vượt budget thì reject, timeout hay áp policy nào?
4Introspection, CORS và CSRFSchema được khám phá ở môi trường nào? Browser dùng cookie hay bearer? Origin, credential, CSRF token và WebSocket/HTTP boundary được kiểm soát ra sao?
5Logging và tracingCó nối được một operation với resolver, downstream và error path bằng correlation ID mà không làm lộ token, variables hoặc PII không?
6Metrics và health checkDashboard và alert có cho thấy latency, error rate, query reject, dependency health, active subscription và cleanup leak mà không tạo cardinality quá cao không?

Trong thực tế, có thể triển khai một số edge control song song để giảm rủi ro sớm. Tuy nhiên, đừng xây authorization dựa trên principal chưa xác minh, đừng tối ưu cost khi chưa biết scope quyền và đừng coi telemetry là bằng chứng protection đã hoạt động. Điểm kết thúc của mỗi bước nên là một policy được viết rõ, một negative test và một tín hiệu quan sát được.

Các điểm cần quyết định

Trước khi sao chép một snippet, hãy ghi lại các quyết định sau trong design hoặc runbook của project:

Điểm quyết địnhCác lựa chọn thường gặpTrade-off cần chốt
Credential và sessionJWT/OAuth2 bearer, session cookie hoặc kết hợpCách truyền qua HTTP/WebSocket, expiry, refresh, revoke, token leakage và CSRF khi dùng cookie
Boundary authorizationRoot operation, field resolver, service, repository, database policy hoặc nhiều lớpĐộ chi tiết với chi phí kiểm tra; phải bảo vệ cả đường gọi không đi qua GraphQL
Query budgetDepth/complexity động, persisted/allowlisted operation, page cap, timeout, rate limitBảo vệ API mở và client linh hoạt khác nhau; role/tenant có thể có budget khác nhau
IntrospectionMở ở local/dev, hạn chế ở production, allowlist theo tool hoặc environmentDeveloper experience so với schema reconnaissance; không nhầm exposure policy với access control
Browser credentialCookie với SameSite/CSRF, bearer header hoặc BFFKhả năng bị CSRF, CORS credentials, refresh token và behavior của client/browser
Telemetry payloadRaw query/variables để debug, hoặc operation name/hash và field allowlistDebuggability so với rò rỉ secret/PII; sampling và retention phải có owner
Metric dimensionsOperation/query hash, field nhóm, status/code bucketChi tiết điều tra so với cardinality, chi phí lưu trữ và dashboard ổn định
Health semanticsLiveness tối giản, readiness gồm dependency, health riêng cho GraphQLTránh restart loop hoặc nhận traffic khi dependency bắt buộc chưa sẵn sàng; không lộ chi tiết nội bộ

Một quyết định tốt luôn nêu scope, owner, failure behavior và cách đo. Ví dụ, “giới hạn query nặng” chưa đủ; cần biết giới hạn theo operation hay principal, trả error code nào, log/metric nào ghi nhận và client nên sửa hay retry.

Checklist trước production

Identity và authorization

  • Credential được xác minh ở boundary phù hợp; token hết hạn, sai chữ ký, sai audience hoặc bị thu hồi không được chuyển thành anonymous access ngoài chủ ý.
  • Principal, tenant và authority được server tạo trong context; không dùng tenantId, userId, isAdmin hoặc permission flag từ variables làm nguồn quyền.
  • Có policy nhất quán cho HTTP, WebSocket/subscription và các đường gọi khác nếu chúng dùng cùng service.
  • Authorization được kiểm tra ở operation/field/resource khi cần và được defense in depth ở service/repository/event source.
  • Lỗi 401/403 hoặc GraphQL error không làm lộ resource tồn tại ngoài phạm vi caller; message không chứa stack trace, SQL, token hoặc PII.
  • Test có ít nhất hai principal/tenant, token thiếu/sai/hết hạn, field nhạy cảm và resource không thuộc quyền.

Query và resource budget

  • Có giới hạn depth và complexity/cost tính đến nested field, fragment, alias, list size, relation, pagination và field đắt.
  • first/limit, số nhánh filter, số phần tử in, timeout và rate limit có minimum/maximum cùng error behavior rõ ràng.
  • Query vượt budget bị từ chối hoặc dừng theo policy trước khi tạo công việc downstream không cần thiết; không âm thầm clamp nếu client không biết.
  • Authorization/tenant filter được áp dụng trước pagination, count, cursor và query data source.
  • Có test query sâu, query nhiều alias/fragment, page lớn, N+1, timeout và retry; không coi query parse được là query an toàn.
  • Nếu dùng persisted/allowlisted operation, cache key và policy có tính đến principal/tenant khi response không public.

HTTP browser và schema exposure

  • Introspection và công cụ khám phá API có policy theo environment; việc hạn chế schema không được dùng để thay authorization.
  • CORS dùng allowlist origin cụ thể, kiểm tra credentials/headers/methods và không dùng * cùng credential nếu policy không cho phép.
  • Nếu dùng cookie cho request có side effect, CSRF token/Origin/SameSite policy đã được thiết kế và test; CORS một mình không chống CSRF.
  • Nếu dùng bearer token, token không nằm trong URL hoặc log; TLS, refresh và redaction được kiểm tra ở mọi transport.
  • HTTP route, WebSocket route, proxy timeout, Upgrade header và protocol được kiểm tra riêng khi có subscription; @SubscriptionMapping không tự bật transport.
  • Status code, GraphQL errors, data/partial data và retry behavior đã được tài liệu hóa; client không retry mù mutation sau timeout.

Telemetry và response safety

  • Log có operation name hoặc query hash, duration, outcome, error code/path và correlation ID đã làm sạch; không log nguyên variables, context, token, cookie hoặc source object.
  • Trace nối được gateway/interceptor, GraphQL execution, resolver, service và downstream; span attributes có sampling, redaction và cardinality phù hợp.
  • Metric theo dõi latency, error rate, auth denial, query reject, timeout, active connection/operation, queue/consumer và cleanup; không dùng raw ID/email/query text làm label.
  • Health/liveness/readiness có semantics rõ, timeout dependency và response không tiết lộ secret hoặc topology nhạy cảm.
  • Error response dùng code/path ổn định khi client cần hành động; message public không phải nơi chứa stack trace hoặc dữ liệu debug.
  • Retention và access policy của log/trace/metric phù hợp với dữ liệu principal, tenant và PII mà hệ thống có thể xử lý.

Verification và vận hành

  • Có contract/integration test cho auth, authorization, depth/complexity, introspection, CORS, CSRF, redaction và error mapping trong backend thật.
  • Có dashboard và alert cho auth failure spike, forbidden spike, query rejection, latency/error SLO, downstream failure và resource leak.
  • Có runbook nêu cách lấy correlation ID, query hash, error path, trace và version/configuration mà không yêu cầu log secret.
  • Rollout có canary/feature flag hoặc ngưỡng rollback khi guardrail từ chối nhầm query hợp lệ; policy production không được chỉ tồn tại trong snippet.
  • Mọi property, interceptor, exception resolver, WebSocket hook và Actuator endpoint đã được đối chiếu với version Spring Boot/Spring for GraphQL thực tế.
  • Static docs được phân biệt với bằng chứng runtime: chỉ test backend, proxy và telemetry thật mới xác nhận security/observability đã hoạt động.

Bước tiếp theo

Chọn nhánh tiếp theo theo khoảng trống hiện tại:

On this page