release: 2026-03-25 (보안 수정 - 프록시 캐시 권한 우회 방지) #31

병합
htlee develop 에서 main 로 4 commits 를 머지했습니다 2026-03-25 14:39:55 +09:00
2개의 변경된 파일19개의 추가작업 그리고 10개의 파일을 삭제
Showing only changes of commit a6220c51d3 - Show all commits

파일 보기

@ -142,8 +142,10 @@ public class AuthController {
}) })
@GetMapping("/check") @GetMapping("/check")
public ResponseEntity<Void> checkProxyAuth(HttpServletRequest request, HttpServletResponse response) { public ResponseEntity<Void> checkProxyAuth(HttpServletRequest request, HttpServletResponse response) {
String targetUri = request.getHeader("X-Original-URI");
String proxyCacheToken = getCookieValue(request, "gc_proxy_auth"); String proxyCacheToken = getCookieValue(request, "gc_proxy_auth");
if (jwtTokenProvider.validateProxyCacheToken(proxyCacheToken) != null) { if (jwtTokenProvider.validateProxyCacheToken(proxyCacheToken, targetUri) != null) {
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
@ -154,7 +156,6 @@ public class AuthController {
Long userId = jwtTokenProvider.getUserIdFromToken(sessionToken); Long userId = jwtTokenProvider.getUserIdFromToken(sessionToken);
String email = jwtTokenProvider.getEmailFromToken(sessionToken); String email = jwtTokenProvider.getEmailFromToken(sessionToken);
String targetUri = request.getHeader("X-Original-URI");
User user = userRepository.findByIdWithRoles(userId).orElse(null); User user = userRepository.findByIdWithRoles(userId).orElse(null);
if (user == null || user.getStatus() != com.gcsc.guide.entity.UserStatus.ACTIVE) { if (user == null || user.getStatus() != com.gcsc.guide.entity.UserStatus.ACTIVE) {
@ -166,7 +167,7 @@ public class AuthController {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); 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) ResponseCookie cacheCookie = ResponseCookie.from("gc_proxy_auth", cacheToken)
.path("/") .path("/")
.httpOnly(true) .httpOnly(true)

파일 보기

@ -68,26 +68,34 @@ public class JwtTokenProvider {
return expirationMs; return expirationMs;
} }
public String generateProxyCacheToken(Long userId) { public String generateProxyCacheToken(Long userId, String targetUri) {
long expiry = Instant.now().plusMillis(expirationMs).getEpochSecond(); 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); String hmac = hmacSha256(payload);
return payload + ":" + hmac; return payload + ":" + hmac;
} }
public Long validateProxyCacheToken(String token) { public Long validateProxyCacheToken(String token, String targetUri) {
if (token == null || token.isBlank()) { if (token == null || token.isBlank()) {
return null; return null;
} }
try { try {
String[] parts = token.split(":"); String[] parts = token.split(":");
if (parts.length != 3) { if (parts.length != 4) {
return null; return null;
} }
long userId = Long.parseLong(parts[0]); long userId = Long.parseLong(parts[0]);
long expiry = Long.parseLong(parts[1]); String uriHash = parts[1];
String expectedHmac = hmacSha256(userId + ":" + expiry); long expiry = Long.parseLong(parts[2]);
if (!expectedHmac.equals(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; return null;
} }
if (Instant.now().getEpochSecond() > expiry) { if (Instant.now().getEpochSecond() > expiry) {