package com.example.keycloakpattern; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletRequest; @RestController @RequestMapping("/api") public class ApiController { @GetMapping("/public") public Map publicEndpoint() { return Map.of("status", "ok", "service", "keycloak-pattern-api"); } /** * Reflects what actually reached the application after the proxy chain. * *

The reverse proxy contract is defined in {@code docs/reverse-proxy-headers.md} * for a single nginx hop. The lab runs {@code nginx -> Traefik -> pod}, so this * endpoint exists to measure the two-hop result instead of assuming it. * *

{@code scheme}, {@code secure} and {@code requestUrl} are the values Keycloak * uses to build the {@code iss} claim and redirect URLs. If forwarded headers are * lost or rewritten, the mismatch shows up here first. */ @GetMapping("/echo") public Map echo(HttpServletRequest request) { Map> headers = new LinkedHashMap<>(); for (String name : Collections.list(request.getHeaderNames())) { headers.put(name.toLowerCase(), Collections.list(request.getHeaders(name))); } Map response = new LinkedHashMap<>(); response.put("headers", headers); response.put("remoteAddr", request.getRemoteAddr()); // Pod IP. Identifies which replica answered, which is what makes the // host nginx upstream distribution and the sticky-session switch observable. response.put("localAddr", request.getLocalAddr()); response.put("scheme", request.getScheme()); response.put("secure", request.isSecure()); response.put("serverName", request.getServerName()); response.put("serverPort", request.getServerPort()); response.put("requestUrl", request.getRequestURL().toString()); return response; } @GetMapping("/me") public Map currentUser(@AuthenticationPrincipal Jwt jwt) { Map response = new LinkedHashMap<>(); response.put("subject", jwt.getSubject()); response.put("username", jwt.getClaimAsString("preferred_username")); response.put("issuer", jwt.getIssuer()); response.put("audience", jwt.getAudience()); return response; } }