Files
ruoyi-vue-pro/docs/education/migration/issues/EDU-004-tenant-identity-security.md

10 KiB

EDU-004 — Enforce tenant resolution and student identity boundaries

  • Status: done — accepted tenant-locator and Member-principal policy implemented and verified; ingress/IP-only probing throttle remains an operational blocker
  • Type: implementation
  • Phase: 1
  • Blockers: EDU-003

Selected policy from EDU-003

  • Origin then Referer supplies forgeable browser-context evidence, not trusted identity. It binds browser UX inputs but does not prevent non-browser spoofing.
  • Headless clients use tenantHandle, the existing System tenant unique name under an exact constrained public contract; do not call it a Tenant Code.
  • Reject/remove the legacy public tenantName query as a separate compatibility behavior.
  • Production domain/handle agreement is checked; disagreement is a locator conflict.
  • Host identity removes case, whitespace, trailing dot, IPv6 brackets, and all ports.
  • Local fallback is controlled only by yudao.education.tenant-resolution.local-development-enabled, default false; profiles do not implicitly enable it.
  • Canonical System website values for this resolver are normalized host-only strings. Scheme/path/port-bearing stored candidates are not silently normalized and require configuration correction or a separate future Flyway/data ticket.
  • /education/context accepts only an authenticated UserTypeEnum.MEMBER Student Principal.
  • Unknown/disabled/expired failures are identical, but successful resolution still discloses tenant existence. Reuse rate limiting/ingress controls and emit abuse-monitoring metrics.
  • Login-method metadata is Member-owned; remove/deprecate Education loginMethods unless a minimal Member public interface is first proven necessary.
  • Retain generic TenantCommonApi, make its lookup methods required, and add System-owned TenantApiImpl contract tests.

See EDU-003-tenant-resolution-decision.md for rationale and exact threat model.

Implementation result

Implemented the accepted public HTTP contracts for tenant resolution and Member-only Education context. The resolver treats Origin, Referer, hostname, and tenantHandle only as forgeable locator claims; successful responses expose only tenantId and displayName, and unknown/disabled/expired tenants share the exact unavailable envelope. Local fallback is controlled exclusively by the secure-default property, including browser-derived loopback hosts, and only configured local hosts use the Education mapping. Production domains always use the canonical System website lookup. TenantCommonApi lookup methods are now required and System-owned adapter tests prove exact delegation and DTO mapping. Existing TenantSecurityWebFilter production behavior was unchanged and is covered by focused filter-boundary regression tests.

No adequate reusable candidate-probing rate-limit attachment was found. The existing ClientIpRateLimiterKeyResolver includes attacker-controlled method arguments in its key, so attaching it would create per-candidate limits rather than an IP-only probing limit. No Education-only limiter was introduced; ingress/IP-only throttling and alerting remain an operational blocker. The Education module also has no direct generic Micrometer dependency seam, so structured resolver metrics remain part of the same operational blocker rather than adding an Education-only dependency or abstraction.

System currently has no separate public tenant display field: its unique name is both the Public Tenant Handle and the only public label available through TenantCommonApi. Therefore handle-based success returns that same value as displayName; tests now model this real exact-name adapter behavior. Suppressing that value requires a future generic System-owned display-field capability, not an Education workaround.

No database or Flyway change was made or executed.

Student outcome

A student receives minimal login-routing data for an available tenant, while authenticated Education endpoints reject wrong tenants and non-Member principals. The public resolver does not claim that caller-supplied locator headers authenticate tenant identity.

Scope

  • browser-context domain claim selection from Origin, then Referer;
  • requested-host confirmation and domain/handle conflict handling;
  • explicit tenantHandle exact lookup and legacy tenantName rejection/removal;
  • host-only normalization and canonical stored-website behavior;
  • secure-default local-development configuration seam and precedence;
  • Member/student principal enforcement for /education/context;
  • exact public CommonResult contract and safe response fields;
  • minimal generic TenantCommonApi adjustment with System-owned tests;
  • reuse of public resolver rate limiting/ingress controls and structured abuse metrics where an existing seam is available.

Exact external contract

All business outcomes use the framework convention of HTTP 200 OK with CommonResult:

Category HTTP CommonResult.code msg data
Malformed/missing/untrusted/locally forbidden locator 200 1005001003 租户识别请求无效 null
Requested-host/domain/handle conflict 200 1005001008 租户识别信息冲突 null
Unknown, disabled, or expired tenant 200 1005001004 当前租户不可用 null
Success 200 0 empty string object containing only tenantId, displayName; current displayName is System tenant name and therefore equals a successful handle claim

Failure messages and JSON shape never contain the rejected host/handle, lifecycle state, System exception detail, or lookup reason. Unknown, disabled, and expired paths must be byte-shape equivalent after normal serialization and have no intentional timing distinction.

Reuse boundaries

  • Reuse TenantSecurityWebFilter, TenantContextHolder, System Tenant public APIs, Member/System security context, UserTypeEnum, and an existing public rate-limit/ingress seam if present.
  • Do not duplicate tenant tables, token logic, login methods, RBAC, or a generic rate-limiter in Education.
  • Education must not depend on System internal Services, Mappers, or DOs.
  • A non-Education change must be generic, minimal, backward-compatible, and tested in its owning module.
  • If no reusable abuse-control seam exists, record the operational blocker; do not invent an Education-only infrastructure abstraction.

Required TDD tests

Education tenant controller HTTP tests

  • valid production Origin; valid Referer fallback;
  • syntactically valid forged header is treated only as an untrusted claim, with no authenticity assertion;
  • malformed Origin and no attacker-selected fallback: exact HTTP/code/msg/data;
  • Origin/requested-host mismatch: exact conflict contract;
  • arbitrary production hostname without browser evidence: exact invalid contract;
  • explicit case-sensitive tenantHandle with ^[A-Za-z0-9._-]{2,64}$ validation;
  • legacy tenantName query rejected/removed independently;
  • domain/handle agreement and conflict;
  • unknown/disabled/expired exact identical wire shape;
  • case, trailing dot, DNS, IPv4, bracketed IPv6, default and non-default port normalization;
  • local flag absent/false rejects local/request-host fallback;
  • local flag true permits code-less configured local host;
  • local flag true plus handle gives the handle precedence;
  • non-canonical stored website candidate does not match;
  • success exposes only tenantId and System tenant name as displayName; for handle lookup this necessarily equals the submitted handle until System owns a separate public display field; no separate handle field, status, websites, expiry, package, private config, or loginMethods.

Education context HTTP tests

  • unauthenticated context rejected;
  • ADMIN principal rejected;
  • MEMBER principal accepted and IDs derived only from security/tenant contexts.

System contract tests

  • TenantCommonApi lookup methods are required, not optional unsupported defaults;
  • TenantApiImpl exact name and canonical website lookups delegate and map DTOs;
  • non-canonical website candidates are not silently normalized by the Public Tenant Resolution path.

Framework/configuration tests

  • existing missing-tenant and authenticated tenant/header mismatch filter behavior remains active;
  • local-development flag defaults false and is not inferred from a Spring profile;
  • reusable limiter/ingress attachment and structured success/failure-category metric are verified where an existing seam is identified.

Acceptance criteria

  • Client tenantId and userId are never authoritative.
  • Browser headers are documented and implemented as forgeable context claims, not authentication.
  • Public existence disclosure is accepted; only unknown/disabled/expired status is indistinguishable.
  • tenantHandle is not mislabeled as Tenant Code; mutability, case, and format match EDU-003.
  • Legacy tenantName is rejected/removed and covered by a compatibility test.
  • Local fallback uses the named secure-default property and unambiguous precedence.
  • Canonical website compatibility policy is implemented without silent legacy normalization.
  • Exact HTTP/CommonResult code/message/data contracts are asserted.
  • Authenticated Student context enforces Member principal type.
  • Existing framework mismatch checks remain active and are not bypassed.
  • Public abuse-control gap is truthfully recorded; no inadequate or Education-only limiter was introduced.
  • Every non-Education modification has a necessity explanation and focused owning-module tests.
  • API documentation matches implementation and tests.

Verification

Run focused Education, System, framework, and configuration tests as applicable, then:

git diff --check
mvn -pl yudao-server -am -DskipTests clean compile

No database change is in scope. If investigation proves automated website data correction is required, stop that part, create a separate blocked data/Flyway ticket, and invoke flyway-postgresql before any schema/data work.

Risk and rollback

  • Risk: High security, disclosure, and login-routing impact.
  • Rollback: Application/configuration rollback to the previous resolver adapter; keep local mode disabled by default and do not weaken authenticated tenant checks. No destructive tenant-data operation.