The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
146 lines
9.4 KiB
Plaintext
146 lines
9.4 KiB
Plaintext
# 프로파일이 선언하는 세 상한
|
|
* @param maxDepth deepest accepted nesting
|
|
* @param maxArrayElements most elements accepted in one array
|
|
* @param maxStringBytes longest accepted string
|
|
boolean rejectScalarCoercion,
|
|
int maxDepth,
|
|
int maxArrayElements,
|
|
int maxStringBytes) {
|
|
public static WebJsonProfile strict() {
|
|
return new WebJsonProfile(true, true, true, true, true, 64, 100_000, 1_048_576);
|
|
}
|
|
|
|
maxArrayElements() 를 부르는 자리 : (0)
|
|
|
|
# 같은 이름의 필드가 예산 타입에도 있다
|
|
22: * @param maxArrayElements most elements accepted in one JSON array
|
|
33: int maxArrayElements,
|
|
51: private static final int ABSOLUTE_ARRAY_ELEMENTS_MAX = 100_000;
|
|
59: requirePositive(maxArrayElements, "maxArrayElements");
|
|
85: requireAtMost(maxArrayElements, ABSOLUTE_ARRAY_ELEMENTS_MAX, "maxArrayElements");
|
|
110: ABSOLUTE_ARRAY_ELEMENTS_MAX,
|
|
123: && maxArrayElements <= other.maxArrayElements
|
|
프로덕션이 등록하는 값과 그 필드 순서 :
|
|
* @param maxExecutionTime longest this operation may run before its deadline expires
|
|
* @param maxResponseBytes largest response this operation may produce
|
|
*/
|
|
public record WebRequestBudget(
|
|
int maxUriBytes,
|
|
int maxHeaderBytes,
|
|
int maxQueryParameters,
|
|
long maxBodyBytes,
|
|
int maxJsonDepth,
|
|
int maxArrayElements,
|
|
int maxMultipartParts,
|
|
public static WebRequestBudget standard() {
|
|
return new WebRequestBudget(
|
|
2048, 8192, 64, 256L * 1024, 32, 1_000, 16, Duration.ofSeconds(10), 4L * 1024 * 1024);
|
|
}
|
|
절대 상한을 쓰는 술어를 프로덕션에서 부르는 자리 : test · WebRequestBudgetTest.java:43 assertThat(WebRequestBudget.standard().withinPlatformMaximum()).isTrue(); test · WebRequestBudgetTest.java:58 () -> catalog.registerOverride(new WebBudgetProfileName("orders.bulk"), base, tighter)) test · WebRequestBudgetTest.java:66 catalog.registerOverride(
|
|
그 예산 목록은 프로덕션 자동설정이 만들고, 등록하는 예산은 하나다 :
|
|
WebMvcPlatformAutoConfiguration.java:67 WebBudgetCatalog catalog = new WebBudgetCatalog();
|
|
WebMvcPlatformAutoConfiguration.java:68 catalog.register(WebBudgetProfileName.standard(), WebRequestBudget.standard());
|
|
WebFluxPlatformAutoConfiguration.java:85 WebBudgetCatalog catalog = new WebBudgetCatalog();
|
|
WebFluxPlatformAutoConfiguration.java:86 catalog.register(WebBudgetProfileName.standard(), WebRequestBudget.standard());
|
|
WebRequestBudget 의 공개 팩토리 : 1
|
|
|
|
# 그 예산을 강제하는 필터가 실제로 읽는 필드
|
|
budget.maxBodyBytes()
|
|
budget.maxHeaderBytes()
|
|
budget.maxQueryParameters()
|
|
budget.maxResponseBytes()
|
|
budget.maxUriBytes()
|
|
반응형 필터가 자기 파일에서 읽는 필드 :
|
|
budget.maxBodyBytes()
|
|
budget.maxHeaderBytes()
|
|
budget.maxQueryParameters()
|
|
budget.maxUriBytes()
|
|
나머지 둘은 이 감싼 교환에 넘겨 잰다 :
|
|
64: .filter(new BoundedServerWebExchange(exchange, budget))
|
|
budget.maxBodyBytes()
|
|
budget.maxResponseBytes()
|
|
두 필터를 등록하는 자리 :
|
|
webfluxContractTest · ReactiveBudgetFixtureApplication.java
|
|
testkit · BudgetFixtureApplication.java
|
|
|
|
# 유계 팩토리가 파서에 거는 것과 그 이유
|
|
/**
|
|
* Builds the reader whose limits are enforced before a document is materialised.
|
|
*
|
|
* <p>The constraints go on the {@code JsonFactory} rather than being checked after parsing, and
|
|
* that placement is the whole point. A depth limit applied to a parsed tree has already paid for
|
|
* the tree; a nesting bomb is cheap to send and expensive to hold, so the only limit that helps is
|
|
* one the streaming parser refuses to exceed.
|
|
JsonFactoryBuilder builder =
|
|
JsonFactory.builder()
|
|
.streamReadConstraints(
|
|
StreamReadConstraints.builder()
|
|
.maxNestingDepth(profile.maxDepth())
|
|
.maxStringLength(profile.maxStringBytes())
|
|
.build());
|
|
if (profile.rejectDuplicateKeys()) {
|
|
builder.enable(StreamReadFeature.STRICT_DUPLICATE_DETECTION);
|
|
}
|
|
return builder.build();
|
|
|
|
# 파서가 실제로 제공하는 상한 전부
|
|
jackson-core-3.1.5.jar
|
|
maxNestingDepth(int)
|
|
maxDocumentLength(long)
|
|
maxTokenCount(long)
|
|
maxNumberLength(int)
|
|
maxStringLength(int)
|
|
maxNameLength(int)
|
|
유계 팩토리를 부르는 자리와 그것을 부르는 자동설정 :
|
|
WebMvcPlatformAutoConfiguration.java:135 return WebObjectMapperFactory.jsonMapper(
|
|
WebFluxPlatformAutoConfiguration.java:129 return WebObjectMapperFactory.jsonMapper(WebJsonProfile.strict());
|
|
WebObjectMapperFactory.java:59 JsonMapper.builder(BoundedJsonFactory.create(profile))
|
|
그중 이 저장소가 이미 쓰는 것 :
|
|
JacksonMessageCodec.java:213 .maxDocumentLength(maxBytes)
|
|
StrictWebSocketJsonCodec.java:64 .maxDocumentLength(budget.maxMessageBytes())
|
|
WebSocketCborCodec.java:76 .maxDocumentLength(binary.maxMessageBytes())
|
|
WebCborMapperFactory.java:55 .maxDocumentLength(budget.maxBodyBytes())
|
|
WebXmlMapperFactory.java:74 .maxDocumentLength(budget.maxBodyBytes())
|
|
LocalJsonSchemaRegistry.java:623 .maxDocumentLength(limits.maximumEnvelopeBytes())
|
|
같은 어댑터의 두 자리가 받는 예산 타입과 그 출하값 :
|
|
WebCborMapperFactory.java:41 public static ObjectMapper create(CodecBudget budget) {
|
|
WebXmlMapperFactory.java:39 public static ObjectMapper create(CodecBudget budget) {
|
|
WebXmlMapperFactory.java:56 public static ObjectMapper create(CodecBudget budget, XMLInputFactory input) {
|
|
21: int maxBodyBytes,
|
|
43: public static CodecBudget conventional(WebRepresentation representation) {
|
|
여섯 자리를 실제로 만드는 곳 :
|
|
main · JacksonMessageCodec.java:66 public static JacksonMessageCodec of(Map<MessageContractKey, Class<?>> registry) {
|
|
main · JacksonMessageCodec.java:77 public static JacksonMessageCodec of(Map<MessageContractKey, Class<?>> registry, int maxBytes) {
|
|
main · MessagingCoreAutoConfiguration.java:364 dev.caskeleton.messaging.schema.json.JacksonMessageCodec.of(
|
|
test · BinaryCodecRoundTripTest.java:118 new WebSocketCborCodec(
|
|
test · BinaryCodecRoundTripTest.java:64 return new WebSocketCborCodec(
|
|
test · JsonContractRegistryTest.java:108 JacksonMessageCodec.of(Map.of(new MessageContractKey(text, V1), String.class), exact);
|
|
test · JsonContractRegistryTest.java:37 JacksonMessageCodec.of(
|
|
test · JsonContractRegistryTest.java:87 JacksonMessageCodec.of(Map.of(new MessageContractKey(largeList, V1), List.class), 1_024);
|
|
test · JsonSchemaIntegrationEventEncoderTest.java:229 new LocalJsonSchemaRegistry(
|
|
test · JsonSchemaIntegrationEventEncoderTest.java:242 return new LocalJsonSchemaRegistry.SchemaSource(path, bytes, sha256(bytes));
|
|
test · LocalJsonSchemaRegistryTest.java:119 new LocalJsonSchemaRegistry(
|
|
test · LocalJsonSchemaRegistryTest.java:122 new LocalJsonSchemaRegistry.SchemaSource(
|
|
test · LocalJsonSchemaRegistryTest.java:134 new LocalJsonSchemaRegistry.SchemaSource(
|
|
test · LocalJsonSchemaRegistryTest.java:136 assertThatThrownBy(() -> new LocalJsonSchemaRegistry(duplicate, LIMITS))
|
|
test · LocalJsonSchemaRegistryTest.java:346 return new LocalJsonSchemaRegistry(
|
|
test · LocalJsonSchemaRegistryTest.java:355 return new LocalJsonSchemaRegistry(
|
|
test · LocalJsonSchemaRegistryTest.java:356 Map.of(path, new LocalJsonSchemaRegistry.SchemaSource(path, bytes, sha256(bytes))), LIMITS);
|
|
test · LocalJsonSchemaRegistryTest.java:367 return new LocalJsonSchemaRegistry.SchemaSource(path, bytes, sha256(bytes));
|
|
test · MessagingEvidenceManifestSchemaValidator.java:48 new LocalJsonSchemaRegistry(
|
|
test · MessagingEvidenceManifestSchemaValidator.java:51 new LocalJsonSchemaRegistry.SchemaSource(
|
|
test · WebCodecMapperTest.java:109 WebXmlMapperFactory.create(
|
|
test · WebCodecMapperTest.java:163 ObjectMapper mapper = WebCborMapperFactory.create(shallow);
|
|
test · WebCodecMapperTest.java:38 return WebCborMapperFactory.create(CodecBudget.conventional(WebRepresentation.CBOR));
|
|
test · WebCodecMapperTest.java:42 return WebXmlMapperFactory.create(CodecBudget.conventional(WebRepresentation.XML));
|
|
test · WebCodecMapperTest.java:86 () -> WebCborMapperFactory.create(CodecBudget.conventional(WebRepresentation.XML)))
|
|
test · WebSocketJsonWireManifestTest.java:49 new StrictWebSocketJsonCodec(
|
|
그 백엔드 잠금 항목이 실린 구성 :
|
|
tools.jackson.dataformat:jackson-dataformat-cbor:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
|
tools.jackson.dataformat:jackson-dataformat-xml:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
|
|
|
# 표본 애플리케이션은 컬렉션 본문에 크기 제약을 건다
|
|
206- public record BatchCreateRequest(
|
|
207- @Valid
|
|
208: @Size(max = MAX_BATCH_SIZE, message = "batch may not exceed " + MAX_BATCH_SIZE + " items")
|