[๐ŸƒSpring] Spring โ†’ FastAPI ํ”„๋ก์‹œ JSESSIONID ์ „๋‹ฌ ์ด์Šˆ

Spring Boot๊ฐ€ FastAPI ํ”„๋ก์‹œ ์—ญํ• ์„ ํ•  ๋•Œ JSESSIONID๋ฅผ ํ•˜์œ„ ์„œ๋น„์Šค๋กœ ์ „๋‹ฌํ•˜์ง€ ์•Š์•„ ๋ฐœ์ƒํ•œ 401 ๋ฒ„๊ทธ โ€” ์›์ธ ๋ถ„์„๋ถ€ํ„ฐ MockMvc ํ…Œ์ŠคํŠธ ํ•จ์ •๊นŒ์ง€ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

[๐ŸƒSpring] Spring โ†’ FastAPI ํ”„๋ก์‹œ JSESSIONID ์ „๋‹ฌ ์ด์Šˆ

Spring Boot๊ฐ€ FastAPI ์•ž๋‹จ ํ”„๋ก์‹œ ์—ญํ• ์„ ํ•˜๋Š” ๊ตฌ์กฐ์—์„œ, FastAPI๊ฐ€ Spring์„ ์—ญํ˜ธ์ถœํ•  ๋•Œ 401์ด ๋ฐœ์ƒํ–ˆ๋‹ค. ์›์ธ์€ JSESSIONID๋ฅผ FastAPI๋กœ ์ „๋‹ฌํ•˜์ง€ ์•Š์€ ๊ฒƒ์ด์—ˆ๊ณ , MockMvc๋กœ ์žฌํ˜„ํ•˜๋Š” ๊ณผ์ •์—์„œ๋„ requestedSessionId๋ฅผ ์ง์ ‘ ์„ค์ •ํ•ด์•ผ ํ•˜๋Š” ํ•จ์ •์ด ์žˆ์—ˆ๋‹ค.

์ด ๊ธ€์—์„œ ๋‹ค๋ฃจ๋Š” ๋‚ด์šฉ


  • ์ด ๊ธ€์„ ์ฝ๊ธฐ ์ „์— ์•Œ์•„๋‘๋ฉด ์ข‹์€ ๊ธฐ๋ณธ ๊ฐœ๋…
  • ๋ฌธ์ œ ์ƒํ™ฉ ๋ฐ ์š”์ฒญ ํ๋ฆ„ ์ •๋ฆฌ
  • JSESSIONID ๋ฏธ์ „๋‹ฌ์˜ ๊ทผ๋ณธ ์›์ธ
  • HttpServletRequest๋กœ ์ฟ ํ‚ค ํฌ์›Œ๋”ฉ ๊ตฌํ˜„
  • MockMvc์—์„œ getRequestedSessionId()๊ฐ€ null์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ด์œ ์™€ ํ•ด๊ฒฐ๋ฒ•

์ด ๊ธ€์„ ์ฝ๊ธฐ ์ „ ํ•„์š”ํ•œ ๋ฐฐ๊ฒฝ


์ด ๊ธ€์€ Spring Boot๊ฐ€ FastAPI ์•ž๋‹จ์—์„œ ํ”„๋ก์‹œ ์—ญํ• ์„ ํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ์ „์ œ๋กœ ํ•œ๋‹ค. ํ•ต์‹ฌ์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ FastAPI๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ , ํ•ญ์ƒ Spring์„ ๋จผ์ € ํ˜ธ์ถœํ•œ๋‹ค๋Š” ์ ์ด๋‹ค.

1
2
3
๋ธŒ๋ผ์šฐ์ €
  โ””โ”€โ–บ Spring Boot
        โ””โ”€โ–บ FastAPI

์ด ๊ตฌ์กฐ์—์„œ Spring์€ ์‚ฌ์šฉ์ž ์„ธ์…˜ ๊ฒ€์ฆ๊ณผ API ์ง„์ž…์ ์„ ๋‹ด๋‹นํ•˜๊ณ , FastAPI๋Š” ์ฑ—๋ด‡ยท์ถ”์ฒœ ๊ฐ™์€ ๋‚ด๋ถ€ ๊ธฐ๋Šฅ์„ ๋‹ด๋‹นํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ํ”„๋ก ํŠธ์—”๋“œ๋Š” FastAPI ์ฃผ์†Œ๋‚˜ ๋‚ด๋ถ€ ์ธ์ฆ ํ‚ค๋ฅผ ์•Œ ํ•„์š”๊ฐ€ ์—†๋‹ค.

Spring Proxy


๐Ÿ“š Proxy: ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์„ ๋Œ€์‹  ๋ฐ›์•„์„œ ๋‹ค๋ฅธ ์„œ๋ฒ„๋กœ ์ „๋‹ฌํ•˜๋Š” ์ค‘๊ฐ„ ์„œ๋ฒ„
์—ฌ๊ธฐ์„œ๋Š” Spring Boot๊ฐ€ ํ”„๋ก์‹œ ์—ญํ• ์„ ํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ํ”„๋ก ํŠธ๊ฐ€ ์•„๋ž˜ Spring API๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด:

1
POST /api/v1/chat/sessions/1/messages

Spring์€ ๋‚ด๋ถ€์—์„œ FastAPI์˜ ์‹ค์ œ API๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

1
POST http://nutriagent-fastapi:8000/api/v1/chat/sessions/1/messages

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” Spring๋งŒ ๋ฐ”๋ผ๋ณด๊ณ , FastAPI๋Š” Docker ๋‚ด๋ถ€ ์„œ๋น„์Šค๋กœ ์ˆจ๊ธธ ์ˆ˜ ์žˆ๋‹ค.

X-Internal-Key


๐Ÿ“š X-Internal-Key: ์„œ๋ฒ„๋ผ๋ฆฌ๋งŒ ๊ณต์œ ํ•˜๋Š” ๋‚ด๋ถ€ ์ธ์ฆ ํ—ค๋”
์‚ฌ์šฉ์ž ์ธ์ฆ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์•„๋‹ˆ๋ผ, ์ด ์š”์ฒญ์ด ์™ธ๋ถ€ ์‚ฌ์šฉ์ž๊ฐ€ ์•„๋‹ˆ๋ผ Spring ๊ฐ™์€ ์‹ ๋ขฐ๋œ ๋‚ด๋ถ€ ์„œ๋น„์Šค์—์„œ ์˜จ ์š”์ฒญ์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ’์ด๋‹ค.

1
Spring โ”€โ”€ X-Internal-Key โ”€โ”€โ–บ FastAPI

๋ธŒ๋ผ์šฐ์ €์—๋Š” ์ด ๊ฐ’์„ ์ ˆ๋Œ€ ๋‚ด๋ ค์ฃผ์ง€ ์•Š๋Š”๋‹ค. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์•Œ์•„์•ผ ํ•  ์ธ์ฆ ์ •๋ณด๋Š” ์„ธ์…˜ ์ฟ ํ‚ค๋ฟ์ด๋‹ค.

1
2
3
4
5
6
๋ธŒ๋ผ์šฐ์ € -> Spring
  Cookie: JSESSIONID=...

Spring -> FastAPI
  X-Internal-Key: ...
  X-Guest-Id: ...

๋งŒ์•ฝ FastAPI๊ฐ€ ์™ธ๋ถ€์— ์—ด๋ ค ์žˆ๊ณ  X-Internal-Key ๊ฒ€์ฆ์ด ์—†๋‹ค๋ฉด, ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ Spring์„ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  FastAPI๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด Spring์˜ ์„ธ์…˜ ๊ฒ€์ฆ์„ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ FastAPI chat router์—๋Š” Depends(verify_internal_call)์„ ๋ถ™์—ฌ ๋‚ด๋ถ€ ํ‚ค๋ฅผ ๊ฒ€์ฆํ–ˆ๋‹ค.

JSESSIONID


๐Ÿ“š JSESSIONID: Spring์˜ HTTP ์„ธ์…˜์„ ์‹๋ณ„ํ•˜๋Š” ์ฟ ํ‚ค
๋ธŒ๋ผ์šฐ์ €๊ฐ€ Spring์— ์š”์ฒญํ•  ๋•Œ ์ด ์ฟ ํ‚ค๋ฅผ ๋ณด๋‚ด๋ฉด, Spring์€ ์„œ๋ฒ„์— ์ €์žฅ๋œ ์„ธ์…˜์—์„œ GUEST_ID ๊ฐ™์€ ๊ฐ’์„ ๊บผ๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

1
Cookie: JSESSIONID=abc123

์ด ํ”„๋กœ์ ํŠธ์˜ Spring ์ปจํŠธ๋กค๋Ÿฌ๋“ค์€ @GuestId๋ฅผ ํ†ตํ•ด ์„ธ์…˜์—์„œ guest id๋ฅผ ๊บผ๋‚ธ๋‹ค. ์ค‘์š”ํ•œ ์ ์€ @GuestId๊ฐ€ ๋‹จ์ˆœํžˆ X-Guest-Id ํ—ค๋”๋ฅผ ๋ฏฟ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, Spring์˜ HttpSession์„ ๋ณธ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

1
2
HttpSession session = request.getSession(false);
String guestId = session != null ? (String) session.getAttribute("GUEST_ID") : null;

๋”ฐ๋ผ์„œ FastAPI๊ฐ€ ๋‚˜์ค‘์— Spring์„ ๋‹ค์‹œ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค๋ฉด, FastAPI๋„ ์œ ํšจํ•œ JSESSIONID๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.

Docker ports์™€ expose


Docker Compose์—์„œ ports์™€ expose๋Š” ๋‹ค๋ฅด๋‹ค.

1
2
ports:
  - "8000:8000"

ports๋Š” ํ˜ธ์ŠคํŠธ ์™ธ๋ถ€๋กœ ํฌํŠธ๋ฅผ ๊ณต๊ฐœํ•œ๋‹ค. ์„œ๋ฒ„ IP์™€ ํฌํŠธ๋ฅผ ์•Œ๋ฉด ์™ธ๋ถ€์—์„œ๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
expose:
  - "8000"

expose๋Š” ๊ฐ™์€ Docker ๋„คํŠธ์›Œํฌ ์•ˆ์˜ ์ปจํ…Œ์ด๋„ˆ๋ผ๋ฆฌ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค. ์ด๋ฒˆ ๊ตฌ์กฐ์—์„œ๋Š” ํ”„๋ก ํŠธ๊ฐ€ FastAPI๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, FastAPI๋Š” ports ๋Œ€์‹  expose๋งŒ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด ์•ˆ์ „ํ•˜๋‹ค.

๋ฌธ์ œ ์ƒํ™ฉ


Spring Boot๊ฐ€ FastAPI ์•ž๋‹จ ํ”„๋ก์‹œ ์—ญํ• ์„ ํ•˜๋Š” ๊ตฌ์กฐ์—์„œ, ์ฑ—๋ด‡ ๋ฉ”์‹œ์ง€ ์ „์†ก API(POST /sessions/{id}/messages)๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด FastAPI๊ฐ€ Spring์˜ onboarding ์—”๋“œํฌ์ธํŠธ๋ฅผ ์—ญ์œผ๋กœ ํ˜ธ์ถœํ•  ๋•Œ 401 ์ด ๋ฐ˜ํ™˜๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

ํ๋ฆ„์„ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
๋ธŒ๋ผ์šฐ์ € โ”€โ”€JSESSIONIDโ”€โ”€โ–บ Spring :8080
                          โ”‚
                          โ”‚ (JSESSIONID ๋ฏธ์ „๋‹ฌ)
                          โ–ผ
                       FastAPI :8000
                          โ”‚
              onboarding tool ํ˜ธ์ถœ ์‹œ
                          โ”‚ JSESSIONID=""
                          โ–ผ
                       Spring /onboarding
                          โ”‚
                          โ””โ”€โ–บ 401 Unauthorized (์„ธ์…˜ ์—†์Œ)

FastAPI์˜ send_message ์—”๋“œํฌ์ธํŠธ๋Š” JSESSIONID ์ฟ ํ‚ค๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„ onboarding tool์˜ context์— ์ „๋‹ฌํ•œ๋‹ค.

1
2
# router.py
jsessionid: str | None = Cookie(None, alias="JSESSIONID")

onboarding tool์€ ์ด ๊ฐ’์„ Spring ์—ญํ˜ธ์ถœ ์‹œ ์ฟ ํ‚ค๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

1
2
# tools/onboarding.py
cookies={"JSESSIONID": context.get("jsessionid", "")}

๋ฌธ์ œ๋Š” Spring ChatProxyController๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ์š”์ฒญ์˜ JSESSIONID๋ฅผ FastAPI๋กœ ์ „๋‹ฌํ•˜์ง€ ์•Š์•˜๋‹ค ๋Š” ๊ฒƒ์ด๋‹ค. FastAPI๊ฐ€ ๋ฐ›๋Š” jsessionid๋Š” ํ•ญ์ƒ None์ด์—ˆ๊ณ , context.get("jsessionid", "") ๋Š” ๋นˆ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ–ˆ๋‹ค.

์›์ธ ๋ถ„์„


ChatProxyController์˜ sendMessage, streamMessage๋Š” FastAPI๋กœ ๋ณด๋‚ด๋Š” ํ—ค๋”๋ฅผ ์ง์ ‘ ๊ตฌ์„ฑํ•œ๋‹ค. ๋‹น์‹œ ์ฝ”๋“œ๋Š” X-Guest-Id์™€ X-Internal-Key๋งŒ ์„ค์ •ํ–ˆ๊ณ , ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ณด๋‚ธ JSESSIONID๋ฅผ Cookie ํ—ค๋”๋กœ ํฌ์›Œ๋”ฉํ•˜๋Š” ๋กœ์ง์ด ์—†์—ˆ๋‹ค.

1
2
3
4
5
6
7
// ์ˆ˜์ • ์ „: JSESSIONID ์—†์Œ
private HttpHeaders buildGuestHeaders(String guestId) {
    HttpHeaders headers = new HttpHeaders();
    headers.set("X-Guest-Id", guestId);
    headers.set("X-Internal-Key", internalApiKey);
    return headers;
}

Spring์€ ์ž์ฒด HttpSession์—์„œ guestId๋ฅผ ๊บผ๋‚ด๋Š” ๋ฐฉ์‹์ด๋ผ JSESSIONID๋ฅผ ์ง์ ‘ ๋‹ค๋ฃจ์ง€ ์•Š์•„๋„ ๋๋‹ค. ํ•˜์ง€๋งŒ FastAPI๊ฐ€ ๊ทธ ๊ฐ’์„ ๋‹ค์‹œ Spring์œผ๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ ์„ ๊ณ ๋ คํ•˜์ง€ ๋ชปํ–ˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•


HttpServletRequest๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฃผ์ž…๋ฐ›์•„, getRequestedSessionId() ๋กœ ๋ธŒ๋ผ์šฐ์ € ์„ธ์…˜ ID๋ฅผ ์ถ”์ถœํ•œ ๋’ค FastAPI ์š”์ฒญ์˜ Cookie ํ—ค๋”์— ์ถ”๊ฐ€ํ–ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ChatProxyController.java
@PostMapping("/sessions/{sessionId}/messages")
public ResponseEntity<String> sendMessage(
        @GuestId String guestId,
        @PathVariable Long sessionId,
        @RequestBody Map<String, Object> body,
        HttpServletRequest servletRequest) {          // ์ถ”๊ฐ€

    HttpHeaders headers = buildGuestHeaders(guestId);
    headers.setContentType(MediaType.APPLICATION_JSON);
    forwardSessionCookie(servletRequest, headers);   // ์ถ”๊ฐ€
    ...
}

private void forwardSessionCookie(HttpServletRequest servletRequest, HttpHeaders headers) {
    String jsessionid = servletRequest.getRequestedSessionId();
    if (jsessionid != null) {
        headers.set(HttpHeaders.COOKIE, "JSESSIONID=" + jsessionid);
    }
}

SSE ์ŠคํŠธ๋ฆผ ์—”๋“œํฌ์ธํŠธ(streamMessage)๋Š” RequestCallback ๋žŒ๋‹ค ๋‚ด๋ถ€์—์„œ ํ—ค๋”๋ฅผ ์„ค์ •ํ•˜๋ฏ€๋กœ, ๋žŒ๋‹ค ์บก์ฒ˜๋ฅผ ์œ„ํ•ด ๋กœ์ปฌ ๋ณ€์ˆ˜๋กœ ๋จผ์ € ์ถ”์ถœ ํ•ด์•ผ ํ•œ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@PostMapping(value = "/sessions/{sessionId}/messages/stream", ...)
public ResponseEntity<StreamingResponseBody> streamMessage(...,
        HttpServletRequest servletRequest) {

    String jsessionid = servletRequest.getRequestedSessionId(); // ๋žŒ๋‹ค ๋ฐ–์—์„œ ์บก์ฒ˜

    StreamingResponseBody stream = outputStream -> {
        chatRestTemplate.execute(url, HttpMethod.POST,
            request -> {
                ...
                if (jsessionid != null) {
                    request.getHeaders().set(HttpHeaders.COOKIE, "JSESSIONID=" + jsessionid);
                }
            }, ...);
    };
}

ํ…Œ์ŠคํŠธ ํ•จ์ •: MockMvc์—์„œ getRequestedSessionId()๋Š” null


ํ…Œ์ŠคํŠธ์—์„œ .session(authSession)์œผ๋กœ ์„ธ์…˜์„ ์ฃผ์ž…ํ•˜๋ฉด getRequestedSessionId()๊ฐ€ null์„ ๋ฐ˜ํ™˜ ํ•œ๋‹ค.

MockHttpServletRequest.getRequestedSessionId()๋Š” requestedSessionId ํ•„๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, MockMvc๊ฐ€ .session()์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์ด ํ•„๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ฑ„์šฐ์ง€ ์•Š๋Š”๋‹ค. ์‹ค์ œ ์„œ๋ธ”๋ฆฟ ํ™˜๊ฒฝ์—์„œ๋Š” JSESSIONID ์ฟ ํ‚ค๋ฅผ ํŒŒ์‹ฑํ•ด ์ด ๊ฐ’์„ ์„ค์ •ํ•˜์ง€๋งŒ, MockMvc๋Š” ๊ทธ ๊ณผ์ •์„ ์ƒ๋žตํ•œ๋‹ค.

ํ•ด๊ฒฐ: MockHttpSession์„ ๊ณ ์ • ID๋กœ ์ƒ์„ฑํ•˜๊ณ , RequestPostProcessor๋กœ requestedSessionId๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ • ํ•œ๋‹ค.

1
2
3
4
5
// ํ…Œ์ŠคํŠธ setUp
private static final String SESSION_ID = "test-jsessionid-001";

authSession = new MockHttpSession(null, SESSION_ID); // ๊ณ ์ • ID ์ง€์ •
authSession.setAttribute("GUEST_ID", GUEST_ID);
1
2
3
4
5
6
// ํ…Œ์ŠคํŠธ ์š”์ฒญ
mockMvc.perform(post("/api/v1/chat/sessions/1/messages")
        .session(authSession)
        .with(req -> { req.setRequestedSessionId(SESSION_ID); return req; }) // ๋ช…์‹œ์  ์„ค์ •
        .contentType(MediaType.APPLICATION_JSON)
        .content("{\"message\":\"์•ˆ๋…•\"}"))

๊ทธ๋Ÿฐ ๋‹ค์Œ ํ—ค๋” ์บก์ฒ˜ ํ›„ ๊ฒ€์ฆ:

1
assertThat(headers.getFirst(HttpHeaders.COOKIE)).contains("JSESSIONID=" + SESSION_ID);

์ •๋ฆฌ


  • Spring์ด ์ง์ ‘ ์„ธ์…˜ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋”๋ผ๋„, ํ•˜์œ„ ์„œ๋น„์Šค๊ฐ€ ๊ทธ ์„ธ์…˜์„ ์—ญ์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ๋Š” ์ฟ ํ‚ค ํฌ์›Œ๋”ฉ์„ ๋ช…์‹œ์ ์œผ๋กœ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.
  • MockMvc๋Š” ์‹ค์ œ ์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ์˜ ์ฟ ํ‚ค ํŒŒ์‹ฑ ๊ณผ์ •์„ ์ƒ๋žตํ•œ๋‹ค. getRequestedSessionId()์ฒ˜๋Ÿผ ์ฟ ํ‚ค์—์„œ ํŒŒ์ƒ๋˜๋Š” ๊ฐ’์€ RequestPostProcessor๋กœ ์ˆ˜๋™ ์„ค์ •ํ•ด์•ผ ํ…Œ์ŠคํŠธ๊ฐ€ ๋™์ž‘ํ•œ๋‹ค.