skill/Java.Kotlin

path value에 /가 들어갈때

have a nice day :D 2025. 11. 5. 20:35
반응형

지금 상황 요약하면 이거예요 👇

> 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