From 953f5917d8b79cc9454488d71b5329625394409c Mon Sep 17 00:00:00 2001 From: htlee Date: Wed, 25 Mar 2026 14:38:50 +0900 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=ED=94=84=EB=A1=9D=EC=8B=9C=20?= =?UTF-8?q?=EC=BA=90=EC=8B=9C=20=EC=BF=A0=ED=82=A4=EC=97=90=20URI=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=94=A9=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EA=B6=8C=ED=95=9C=20=EC=9A=B0=ED=9A=8C=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gc_proxy_auth 캐시 토큰에 요청 URI 해시를 포함하여 다른 URL에서 발급된 캐시 쿠키로 권한 없는 서비스에 접근할 수 없도록 수정 - 토큰 형식: userId:expiry:hmac → userId:uriHash:expiry:hmac - generateProxyCacheToken/validateProxyCacheToken에 targetUri 파라미터 추가 - AuthController에서 캐시 검증 전 X-Original-URI 추출 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../com/gcsc/guide/auth/AuthController.java | 7 +++--- .../com/gcsc/guide/auth/JwtTokenProvider.java | 22 +++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/gcsc/guide/auth/AuthController.java b/src/main/java/com/gcsc/guide/auth/AuthController.java index 45ee606..6d8cbb2 100644 --- a/src/main/java/com/gcsc/guide/auth/AuthController.java +++ b/src/main/java/com/gcsc/guide/auth/AuthController.java @@ -142,8 +142,10 @@ public class AuthController { }) @GetMapping("/check") public ResponseEntity checkProxyAuth(HttpServletRequest request, HttpServletResponse response) { + String targetUri = request.getHeader("X-Original-URI"); + String proxyCacheToken = getCookieValue(request, "gc_proxy_auth"); - if (jwtTokenProvider.validateProxyCacheToken(proxyCacheToken) != null) { + if (jwtTokenProvider.validateProxyCacheToken(proxyCacheToken, targetUri) != null) { return ResponseEntity.ok().build(); } @@ -154,7 +156,6 @@ public class AuthController { Long userId = jwtTokenProvider.getUserIdFromToken(sessionToken); String email = jwtTokenProvider.getEmailFromToken(sessionToken); - String targetUri = request.getHeader("X-Original-URI"); User user = userRepository.findByIdWithRoles(userId).orElse(null); if (user == null || user.getStatus() != com.gcsc.guide.entity.UserStatus.ACTIVE) { @@ -166,7 +167,7 @@ public class AuthController { return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } - String cacheToken = jwtTokenProvider.generateProxyCacheToken(userId); + String cacheToken = jwtTokenProvider.generateProxyCacheToken(userId, targetUri); ResponseCookie cacheCookie = ResponseCookie.from("gc_proxy_auth", cacheToken) .path("/") .httpOnly(true) diff --git a/src/main/java/com/gcsc/guide/auth/JwtTokenProvider.java b/src/main/java/com/gcsc/guide/auth/JwtTokenProvider.java index 518b990..0c2b369 100644 --- a/src/main/java/com/gcsc/guide/auth/JwtTokenProvider.java +++ b/src/main/java/com/gcsc/guide/auth/JwtTokenProvider.java @@ -68,26 +68,34 @@ public class JwtTokenProvider { return expirationMs; } - public String generateProxyCacheToken(Long userId) { + public String generateProxyCacheToken(Long userId, String targetUri) { long expiry = Instant.now().plusMillis(expirationMs).getEpochSecond(); - String payload = userId + ":" + expiry; + String uriHash = hmacSha256(targetUri != null ? targetUri : ""); + String payload = userId + ":" + uriHash + ":" + expiry; String hmac = hmacSha256(payload); return payload + ":" + hmac; } - public Long validateProxyCacheToken(String token) { + public Long validateProxyCacheToken(String token, String targetUri) { if (token == null || token.isBlank()) { return null; } try { String[] parts = token.split(":"); - if (parts.length != 3) { + if (parts.length != 4) { return null; } long userId = Long.parseLong(parts[0]); - long expiry = Long.parseLong(parts[1]); - String expectedHmac = hmacSha256(userId + ":" + expiry); - if (!expectedHmac.equals(parts[2])) { + String uriHash = parts[1]; + long expiry = Long.parseLong(parts[2]); + + String expectedUriHash = hmacSha256(targetUri != null ? targetUri : ""); + if (!expectedUriHash.equals(uriHash)) { + return null; + } + + String expectedHmac = hmacSha256(userId + ":" + uriHash + ":" + expiry); + if (!expectedHmac.equals(parts[3])) { return null; } if (Instant.now().getEpochSecond() > expiry) {