feat: 2홉 구성 진행
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
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;
|
||||
@@ -9,6 +11,8 @@ 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 {
|
||||
@@ -18,6 +22,35 @@ public class ApiController {
|
||||
return Map.of("status", "ok", "service", "keycloak-pattern-api");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflects what actually reached the application after the proxy chain.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>{@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<String, Object> echo(HttpServletRequest request) {
|
||||
Map<String, List<String>> headers = new LinkedHashMap<>();
|
||||
for (String name : Collections.list(request.getHeaderNames())) {
|
||||
headers.put(name.toLowerCase(), Collections.list(request.getHeaders(name)));
|
||||
}
|
||||
|
||||
Map<String, Object> response = new LinkedHashMap<>();
|
||||
response.put("headers", headers);
|
||||
response.put("remoteAddr", request.getRemoteAddr());
|
||||
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<String, Object> currentUser(@AuthenticationPrincipal Jwt jwt) {
|
||||
Map<String, Object> response = new LinkedHashMap<>();
|
||||
|
||||
@@ -17,7 +17,8 @@ public class SecurityConfig {
|
||||
.sessionManagement(session ->
|
||||
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/actuator/health", "/actuator/health/**", "/api/public")
|
||||
.requestMatchers("/actuator/health", "/actuator/health/**", "/api/public",
|
||||
"/api/echo")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated())
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
server:
|
||||
port: ${SERVER_PORT:8081}
|
||||
# Spring ignores X-Forwarded-* unless this is set, so scheme/secure/requestUrl
|
||||
# report the raw connection by default. Keycloak has the same opt-in as
|
||||
# KC_PROXY_HEADERS. Flipping this to "native" is what the two-hop measurement
|
||||
# compares against.
|
||||
forward-headers-strategy: ${SERVER_FORWARD_HEADERS_STRATEGY:none}
|
||||
|
||||
spring:
|
||||
application:
|
||||
|
||||
@@ -25,6 +25,18 @@ class ApiSecurityTest {
|
||||
.andExpect(jsonPath("$.status").value("ok"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void echoEndpointReflectsForwardedHeadersWithoutAuthentication() throws Exception {
|
||||
mockMvc.perform(get("/api/echo")
|
||||
.header("X-Forwarded-Proto", "https")
|
||||
.header("X-Forwarded-Host", "app1.example.test"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.headers['x-forwarded-proto'][0]").value("https"))
|
||||
.andExpect(jsonPath("$.headers['x-forwarded-host'][0]").value("app1.example.test"))
|
||||
.andExpect(jsonPath("$.requestUrl").exists())
|
||||
.andExpect(jsonPath("$.remoteAddr").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void protectedEndpointRejectsAnonymousRequests() throws Exception {
|
||||
mockMvc.perform(get("/api/me"))
|
||||
|
||||
Reference in New Issue
Block a user