Rolle im AblaufPrüft Tokenfehler, Rollen, Objektzugriff, Verschleierung und Auditierung.
Damit ist der zentrale Pfad abgeschlossen; der Test-/Runner-Code und die vollständige Dateiliste darunter zeigen die übrigen Varianten.
package com.example.securitydeepdive;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.Set;
public final class Run5BTestRunner {
public static void main(String[] args) {
testOwnerMayReadOwnOrder();
testForeignCustomerGetsNotFound();
testSupportMayReadAnyOrder();
testInvalidTokenBecomes401();
testExpiredTokenBecomes401();
testAuditRecordsAllowAndDeny();
testServiceScopePolicy();
System.out.println("RUN5B_TESTS_OK");
}
private static Fixture fixture() {
Clock clock = Clock.fixed(Instant.parse("2026-07-09T12:00:00Z"), ZoneOffset.UTC);
SignedTokenService tokens = new SignedTokenService("run5b-secret", "https://issuer.example", "enterprise-api");
InMemoryOrderRepository repo = new InMemoryOrderRepository();
repo.save(new OrderView(new OrderId("ORD-1"), new CustomerId("customer-1"), "CONFIRMED", "pay_****"));
InMemoryAuditSink audit = new InMemoryAuditSink();
AuthorizationService authz = new AuthorizationService(audit, clock);
return new Fixture(clock, tokens, new SecurityContextFactory(tokens, clock), repo, audit, new OrderQueryService(repo, authz), authz);
}
private static void testOwnerMayReadOwnOrder() {
Fixture f = fixture();
SecurityContext ctx = f.context("customer-1", Set.of(Role.CUSTOMER), Set.of("orders:read"), 3600);
OrderView order = f.query.getOrder(ctx, new OrderId("ORD-1"));
assertEquals("ORD-1", order.orderId().value(), "owner reads own order");
}
private static void testForeignCustomerGetsNotFound() {
Fixture f = fixture();
SecurityContext ctx = f.context("customer-2", Set.of(Role.CUSTOMER), Set.of("orders:read"), 3600);
assertThrows(NotFoundException.class, () -> f.query.getOrder(ctx, new OrderId("ORD-1")), "foreign customer hidden as not found");
}
private static void testSupportMayReadAnyOrder() {
Fixture f = fixture();
SecurityContext ctx = f.context("support-99", Set.of(Role.SUPPORT), Set.of("orders:read"), 3600);
OrderView order = f.query.getOrder(ctx, new OrderId("ORD-1"));
assertEquals("CONFIRMED", order.status(), "support reads any order");
}
private static void testInvalidTokenBecomes401() {
Fixture f = fixture();
SecurityExceptionMapper mapper = new SecurityExceptionMapper();
try {
f.factory.fromBearerToken("Bearer invalid.token.value", "corr-401", "127.0.0.1");
fail("invalid token must fail");
} catch (UnauthenticatedException ex) {
ProblemDetails problem = mapper.toProblem(ex, "corr-401");
assertEquals(401, problem.status(), "invalid token maps to 401");
assertTrue(!problem.detail().contains("signature"), "internal token details not leaked");
}
}
private static void testExpiredTokenBecomes401() {
Fixture f = fixture();
String token = f.tokens.issue(new TokenClaims("customer-1", "tenant-main", "https://issuer.example", "enterprise-api", Set.of(Role.CUSTOMER), Set.of("orders:read"), f.clock.instant().minusSeconds(1)));
assertThrows(UnauthenticatedException.class, () -> f.factory.fromBearerToken("Bearer " + token, "corr-exp", "127.0.0.1"), "expired token rejected");
}
private static void testAuditRecordsAllowAndDeny() {
Fixture f = fixture();
SecurityContext owner = f.context("customer-1", Set.of(Role.CUSTOMER), Set.of("orders:read"), 3600);
f.query.getOrder(owner, new OrderId("ORD-1"));
SecurityContext stranger = f.context("customer-2", Set.of(Role.CUSTOMER), Set.of("orders:read"), 3600);
assertThrows(NotFoundException.class, () -> f.query.getOrder(stranger, new OrderId("ORD-1")), "deny audited");
assertEquals(2, f.audit.events().size(), "allow and deny audit events");
assertEquals("ALLOW", f.audit.events().get(0).decision(), "first allow");
assertEquals("DENY", f.audit.events().get(1).decision(), "second deny");
}
private static void testServiceScopePolicy() {
Fixture f = fixture();
SecurityContext service = f.context("payment-service", Set.of(Role.PAYMENT_SERVICE), Set.of("payments:authorize"), 3600);
ServiceTokenPolicy policy = new ServiceTokenPolicy("payments:authorize");
f.authz.require(service, "payment.authorize", "PAY-1", "payment.authorize", policy);
assertEquals("ALLOW", f.audit.events().get(0).decision(), "service scope allowed");
}
private static final class Fixture {
final Clock clock;
final SignedTokenService tokens;
final SecurityContextFactory factory;
final InMemoryOrderRepository repo;
final InMemoryAuditSink audit;
final OrderQueryService query;
final AuthorizationService authz;
Fixture(Clock clock, SignedTokenService tokens, SecurityContextFactory factory, InMemoryOrderRepository repo, InMemoryAuditSink audit, OrderQueryService query, AuthorizationService authz) {
this.clock = clock; this.tokens = tokens; this.factory = factory; this.repo = repo; this.audit = audit; this.query = query; this.authz = authz;
}
SecurityContext context(String subject, Set<Role> roles, Set<String> scopes, long validSeconds) {
String token = tokens.issue(new TokenClaims(subject, "tenant-main", "https://issuer.example", "enterprise-api", roles, scopes, clock.instant().plusSeconds(validSeconds)));
return factory.fromBearerToken("Bearer " + token, "corr-test", "127.0.0.1");
}
}
private static void assertEquals(Object expected, Object actual, String message) {
if (!java.util.Objects.equals(expected, actual)) throw new AssertionError(message + " expected=" + expected + " actual=" + actual);
}
private static void assertTrue(boolean condition, String message) { if (!condition) throw new AssertionError(message); }
private static void fail(String message) { throw new AssertionError(message); }
private static void assertThrows(Class<? extends Throwable> expected, Runnable action, String message) {
try { action.run(); } catch (Throwable t) { if (expected.isInstance(t)) return; throw new AssertionError(message + " wrong exception=" + t); }
throw new AssertionError(message + " no exception");
}
}