๐Ÿ› ๏ธ ์†Œ์…œ ๋กœ๊ทธ์ธ ํ›„ access token? ์•Œ๊ณ  ๋ณด๋‹ˆ SSE ์ „์šฉ ํ† ํฐ์ด์—ˆ๋‹ค

oversleepยท2025๋…„ 6์›” 9์ผ
0

troubleshooting

๋ชฉ๋ก ๋ณด๊ธฐ
19/19

์ตœ๊ทผ ํ”„๋กœ์ ํŠธ์—์„œ ์†Œ์…œ ๋กœ๊ทธ์ธ ํ›„ access token ์ €์žฅ ๋ฐ ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋˜ ์ค‘,
localStorage์— ์ €์žฅ๋œ access token์ด ์ด์ƒํ•˜๊ฒŒ **[object Object]**๋กœ ์ €์žฅ๋˜๊ณ ,
API ์š”์ฒญ์— ์ธ์ฆ์ด ์ œ๋Œ€๋กœ ๋˜์ง€ ์•Š๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ–ˆ๋‹ค.

์ฒ˜์Œ์—” ๋‹จ์ˆœํ•œ ๋ฒ„๊ทธ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์ง€๋งŒ, ์ด ๋ฌธ์ œ๋ฅผ ์ถ”์ ํ•˜๋ฉด์„œ
๋‚˜๋Š” "access token", "SSE token", "์‘๋‹ต ํ—ค๋” vs ๋ฐ”๋””" ๊ฐœ๋…์„ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ์žˆ์ง€ ์•Š์•˜๋‹ค๋Š” ๊ฑธ ๊นจ๋‹ฌ์•˜๋‹ค.


๐Ÿ” ๋ฌธ์ œ์˜ ์‹œ์ž‘: access token์ด [object Object]๋กœ ์ €์žฅ๋จ

์ฒ˜์Œ์—” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋กœ access token์„ ์ €์žฅํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

const response = await axiosInstance.get('/api/jwt/access-token');
localStorage.setItem('access_token', response.data.data);

์ด๋•Œ response.data.data๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•ํƒœ์˜€๋‹ค:

{
  tokenType: 'SSE',
  value: 'eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6...'
}

์ฆ‰, ๋‚˜๋Š” ํ† ํฐ ๊ฐ์ฒด ์ „์ฒด๋ฅผ ๋ฌธ์ž์—ด ์—†์ด ์ €์žฅํ•ด์„œ [object Object]๊ฐ€ ๋“ค์–ด๊ฐ„ ๊ฑฐ์˜€๋‹ค.


๐Ÿง  ๊ทธ๋ฆฌ๊ณ  ๋” ์ค‘์š”ํ•œ ๊ฑธ ๋†“์น˜๊ณ  ์žˆ์—ˆ๋‹ค: ์ด๊ฑด SSE ์ „์šฉ ํ† ํฐ์ด์—ˆ๋‹ค

๋ฌธ์ œ๋Š” ๋‹จ์ˆœํžˆ stringify๋ฅผ ์•ˆ ํ–ˆ๋‹ค๋Š” ๋ฐ ์žˆ์ง€ ์•Š์•˜๋‹ค.
๋ฐฑ์—”๋“œ ํŒ€์›์—๊ฒŒ ๋ฌผ์–ด๋ณธ ๊ฒฐ๊ณผ, ๋‚ด๊ฐ€ ์ €์žฅํ•˜๊ณ  ์žˆ๋Š” value๋Š”
์ผ๋ฐ˜ access token์ด ์•„๋‹ˆ๋ผ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ(SSE) ์—ฐ๊ฒฐ์„ ์œ„ํ•œ ์ „์šฉ ํ† ํฐ์ด์—ˆ๋‹ค.

๋ฐฑ์—”๋“œ ๊ตฌ์กฐ๋Š” ์ด๋ ‡๊ฒŒ ๋˜์–ด ์žˆ์—ˆ๋‹ค:

  • ํ—ค๋” (Authorization): ์ผ๋ฐ˜ access token
  • ์ฟ ํ‚ค (Set-Cookie): refresh token
  • ๋ฐ”๋”” (data.value): SSE ์•Œ๋ฆผ ์ „์šฉ ํ† ํฐ

๋‚˜๋Š” ๋ฐ”๋””์— ๋‹ด๊ธด ํ† ํฐ์„ ์ผ๋ฐ˜ ์ธ์ฆ์šฉ์œผ๋กœ ์ฐฉ๊ฐํ•˜๊ณ  ๋ชจ๋“  API์— ์จ๋ฒ„๋ฆฌ๊ณ  ์žˆ์—ˆ๋˜ ๊ฒƒ.


โœ… ์˜ฌ๋ฐ”๋ฅธ ๊ตฌ์กฐ๋กœ ์ˆ˜์ •

์ˆ˜์ •๋œ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

const response = await axiosInstance.get('/api/jwt/access-token');

// ํ—ค๋”์—์„œ ์ผ๋ฐ˜ access token ์ถ”์ถœ
const authAccessToken = response.headers['authorization']?.replace('Bearer ', '');
if (authAccessToken) {
  localStorage.setItem('access_token', authAccessToken);
}

// ๋ฐ”๋””์—์„œ SSE token ์ถ”์ถœ
const sseToken = response.data.data?.value;
if (sseToken) {
  localStorage.setItem('sse_token', sseToken);
}

์ด์ œ access_token์€ ์ผ๋ฐ˜ API ํ˜ธ์ถœ์— ์‚ฌ์šฉํ•˜๊ณ ,
sse_token์€ ์•Œ๋ฆผ ๊ตฌ๋… ์‹œ ์•„๋ž˜์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•œ๋‹ค:

const eventSource = new EventSource(`/api/notification/subscribe?token=${sseToken}`);

โœ๏ธ ๋‚ด๊ฐ€ ๋ชฐ๋ž๋˜ ๊ฒƒ๋“ค

  1. ์‘๋‹ต ๋ฐ”๋””์™€ ์‘๋‹ต ํ—ค๋”์˜ ์—ญํ•  ์ฐจ์ด

    • ๋ฐ”๋””๋Š” ๋ฐ์ดํ„ฐ
    • ํ—ค๋”๋Š” ์ธ์ฆ ๊ฐ™์€ ๋ฉ”ํƒ€์ •๋ณด
  2. access token์€ ๋ฌด์กฐ๊ฑด ๋ฐ”๋””์—๋งŒ ์žˆ๋Š” ๊ฒŒ ์•„๋‹ˆ๋‹ค
    โ†’ ์˜คํžˆ๋ ค ๋ณด์•ˆ์ƒ์˜ ์ด์œ ๋กœ ํ—ค๋”๋กœ ๋‚ด๋ ค์˜ค๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค

  3. refresh token์€ HttpOnly ์ฟ ํ‚ค๋กœ ์˜ค๊ธฐ ๋•Œ๋ฌธ์— JS์—์„œ ์ ‘๊ทผ ๋ชป ํ•œ๋‹ค

  4. SSE ์—ฐ๊ฒฐ์šฉ ํ† ํฐ์€ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๋ฉฐ, ์ผ๋ฐ˜ ์ธ์ฆ๊ณผ ๋ณ„๋„๋กœ ๋ถ„๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค


๐Ÿ“Œ ๋งˆ๋ฌด๋ฆฌ

์ด ๊ฒฝํ—˜์„ ํ†ตํ•ด ๋‚˜๋Š” ์ธ์ฆ ๊ตฌ์กฐ์˜ ๊ธฐ๋ณธ๊ธฐ๋ฅผ ๋งŽ์ด ๋ฐฐ์› ๋‹ค.
์•ž์œผ๋ก  ๋‹จ์ˆœํžˆ "ํ† ํฐ์ด ์žˆ๋‹ค"๊ณ  ํ•ด์„œ ๋ฌด์กฐ๊ฑด access token์œผ๋กœ ์“ฐ์ง€ ์•Š๊ณ ,
์šฉ๋„์— ๋”ฐ๋ผ ๊ตฌ๋ถ„ํ•˜๊ณ  ์ €์žฅ ์œ„์น˜๋„ ๊ตฌ๋ถ„ํ•ด์„œ ๊ด€๋ฆฌํ•  ๊ฒƒ์ด๋‹ค.

profile
๊ถ๊ธˆํ•œ ๊ฒƒ, ํ–ˆ๋˜ ๊ฒƒ, ์‹œํ–‰์ฐฉ์˜ค ๊ทธ๋ฆฌ๊ณ  ๊ธฐ์–ตํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ๋“ค์„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€