지금 상황 요약하면 이거예요 👇
> React 프런트엔드에서 백엔드(Spring Boot)로 통신할 때,
요청 path 파라미터에 /가 들어간 데이터를 보내고 싶다.
(예: /api/files/folder/a/b/info 형태)
그런데 /가 들어가면 경로 분리자로 인식되어 서버에서 400 또는 404가 난다.
---
💡 해결책: Base64URL 인코딩 (추천)
React에서 보낼 때 경로에 들어가는 값을 Base64URL로 인코딩하면 /가 완전히 사라져서 안전하게 path로 전달할 수 있습니다.
✅ React (프론트)
// utils/base64url.js
export function toBase64Url(str) {
return btoa(unescape(encodeURIComponent(str))) // Base64 인코딩
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, ""); // padding 제거
}
// 사용 예시
const path = "folder/a/b/file.txt";
const token = toBase64Url(path);
fetch(`/api/files/${token}/info`) // /나 %2F 문제 없음!
.then(res => res.json())
.then(console.log);
---
✅ Spring Boot (Kotlin 서버)
import org.springframework.web.bind.annotation.*
import java.nio.charset.StandardCharsets
import java.util.Base64
object Base64Url {
fun encode(s: String): String =
Base64.getUrlEncoder().withoutPadding().encodeToString(s.toByteArray(StandardCharsets.UTF_8))
fun decode(token: String): String =
String(Base64.getUrlDecoder().decode(token), StandardCharsets.UTF_8)
}
@RestController
@RequestMapping("/api/files")
class FileApi {
@GetMapping("/{token}/info")
fun info(@PathVariable token: String): Map<String, String> {
val originalPath = Base64Url.decode(token)
return mapOf("path" to originalPath, "type" to "info")
}
}
---
✅ 결과 예시
React 요청:
GET /api/files/Zm9sZGVyL2EvYi9maWxlLnR4dA/info
서버 디코드 결과:
path = "folder/a/b/file.txt"
---
🚀 왜 Base64URL이 좋은가?
문제 Base64URL 사용 시
/ 가 경로 구분자로 처리됨 _ 로 대체되어 안전
%2F 인코딩 시 톰캣/시큐리티 제한 경로 내 특수문자 없음
/path/{*id} 제약 /path/{token}/info 패턴도 문제 없음
React Router, Nginx, Tomcat 등 모든 계층 통과 ✅ 문제 없음
---
🔒 추가 팁
원본을 그대로 보내야 하는 경우엔 쿼리스트링 사용:
/api/files?path=folder/a/b/file.txt
토큰을 사람이 읽을 필요 없으므로 Base64URL이 완벽한 대안.
---
👉 결론:
React → Base64URL 인코딩 → Spring Boot에서 디코딩
이 조합이면 / 포함된 path 데이터를 안전하게 URL 경로로 전달할 수 있습니다.
// Base64UrlFallback.kt
package com.example.util
import org.slf4j.LoggerFactory
import java.nio.charset.StandardCharsets
import java.util.Base64
object Base64UrlFallback {
private val log = LoggerFactory.getLogger(this::class.java)
/**
* token이 Base64URL이면 디코드해서 반환.
* 디코딩에 실패하면 원본 token을 그대로 반환.
*
* - Base64 URL-safe 인코딩: '-' 대신 '+', '_' 대신 '/', 패딩 '='는 없을 수 있음.
* - 안전하게 패딩을 복구해 java Base64 URL 디코더로 디코드 시도.
*/
fun decodeOrOriginal(token: String): String {
// null/empty 체크 (필요하면 원본 반환)
if (token.isEmpty()) return token
try {
// 1) 패딩 복구: length % 4 에 따라 '=' 채움
val padding = (4 - token.length % 4) % 4
val padded = token + "=".repeat(padding)
// 2) URL-safe decoder 사용 (Java가 '-'/'_' 처리함)
val decodedBytes = Base64.getUrlDecoder().decode(padded)
// 3) 바이트를 문자열로 변환 (UTF-8)
val decoded = String(decodedBytes, StandardCharsets.UTF_8)
// 4) (선택) 간단한 검증: 제어문자 제외, 지나치게 긴 값 제한 등
if (looksSuspicious(decoded)) {
log.warn("Decoded value looks suspicious, returning original token. tokenLen=${token.length}, decodedLen=${decoded.length}")
return token
}
return decoded
} catch (ex: IllegalArgumentException) {
// 디코딩 실패 (잘못된 Base64 포맷 등)
log.debug("Base64URL decode failed for token (using original). token='{}', cause='{}'", token, ex.message)
return token
} catch (ex: Exception) {
log.warn("Unexpected error during Base64URL decode - using original token. token='{}'", token, ex)
return token
}
}
// 아주 기본적인 suspicious 체크 (원하면 강화)
private fun looksSuspicious(s: String): Boolean {
// 제어문자(널, 벨 등)가 포함되면 의심
if (s.any { it.isISOControl() }) return true
// 경로 탈출 패턴이 있으면 의심 (../)
if (s.contains("..")) return true
// 너무 길면 의심 (예: 상한 4096)
if (s.length > 4096) return true
return false
}
}
'skill > Java.Kotlin' 카테고리의 다른 글
| 데이터 클래스를 쿼리스트링 변환 (0) | 2025.10.28 |
|---|---|
| Jacoco url 기준 테스트 (0) | 2025.09.16 |
| Jacoco 리포트 화면 (0) | 2025.09.16 |
| jacoco 개념 + 외부 연동 MockkBean 처리 (0) | 2025.09.15 |
| where in 조건 갯수 제한 (2) | 2025.07.30 |