JWT ๊ธฐ๋ฐ ์ธ์ฆ์ ์ฟ ํค/์ธ์ ๋ฐฉ์๊ณผ ์ ์ฌํ๊ฒ JWT ํ ํฐ(Access Token)์ HTTP ํค๋์ ์ค์ด ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ๋ฅผ ์๋ณํ๋ค.
๐ ๊ตฌ์กฐ
JWT๋ Header, Payload, Sigmature ์ธ๊ฐ์ง๋ก ๊ตฌ๋ถ๋๋ค.
Header
{
"alg": "HS256",
"typ": "JWT"
}
- alg: ์ ๋ณด๋ฅผ ์ํธํํ ํด์ฑ ์๊ณ ๋ฆฌ์ฆ
- typ: ํ ํฐ์ ํ์
Payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
- Payload๋ ํ ํฐ์ ๋ด์ ์ ๋ณด๋ฅผ ์ง๋
- ์ฃผ๋ก ํด๋ผ์ด์ธํธ์ ๊ณ ์ ID ๊ฐ ๋ฐ ์ ํจ ๊ธฐ๊ฐ ๋ฑ์ด ํฌํจ
- key-value ํ์์ผ๋ก ์ด๋ฃจ์ด์ง ํ ์์ ์ ๋ณด๋ฅผ ํด๋ ์(Claim)์ด๋ผ๊ณ ํ๋ค
- ํด๋ ์์ ๊ณต๊ฐ(public) ํน์ ๋น๊ณต๊ฐ(private) ํ ๊ฒ์ธ์ง ๋ฑ๋ก(registered)ํ ๊ฒ์ธ์ง ๊ฒฐ์ ํ ์ ์๋ค
Signature
HMACSHA256(
base64UrlEncode(header) + "." +base64UrlEncode(payload),
256-bit-secret
)
- Signature๋ ์ธ์ฝ๋ฉ๋ Header์ Payload๋ฅผ ๋ํ ๋ค ๋น๋ฐํค๋ก ํด์ฑํ์ฌ ์์ฑ
- Header์ Payload๋ ๋จ์ํ ์ธ์ฝ๋ฉ๋ ๊ฐ์ด๊ธฐ ๋๋ฌธ์ ์ 3์๊ฐ ๋ณตํธํ ๋ฐ ์กฐ์ํ ์ ์์ง๋ง, Signature๋ ์๋ฒ ์ธก์์ ๊ด๋ฆฌํ๋ ๋น๋ฐํค๊ฐ ์ ์ถ๋์ง ์๋ ์ด์ ๋ณตํธํํ ์ ์๋ค. → ๋ฐ๋ผ์ Signature๋ ํ ํฐ์ ์๋ณ์กฐ ์ฌ๋ถ๋ฅผ ํ์ธํ๋๋ฐ ์ฌ์ฉ
๐ ๋์๋ฐฉ์
- ํด๋ผ์ด์ธํธ ๋ก๊ทธ์ธ ์์ฒญ์ด ๋ค์ด์ค๋ฉด, ์๋ฒ๋ ๊ฒ์ฆ ํ ํด๋ผ์ด์ธํธ ๊ณ ์ ID ๋ฑ์ ์ ๋ณด๋ฅผ Payload์ ๋ด๋๋ค
- ์ํธํํ ๋น๋ฐํค๋ฅผ ์ฌ์ฉํด Access Token(JWT)์ ๋ฐ๊ธ
- ํด๋ผ์ด์ธํธ๋ ์ ๋ฌ๋ฐ์ ํ ํฐ์ ์ ์ฅํด๋๊ณ , ์๋ฒ์ ์์ฒญํ ๋ ๋ง๋ค ํ ํฐ์ ์์ฒญ ํค๋ Authorization์ ํฌํจ์์ผ ํจ๊ป ์ ๋ฌ
- ์๋ฒ๋ ํ ํฐ์ Signature๋ฅผ ๋น๋ฐํค๋ก ๋ณตํธํํ ๋ค์, ์๋ณ์กฐ ์ฌ๋ถ ๋ฐ ์ ํจ ๊ธฐ๊ฐ ๋ฑ์ ํ์ธ
- ์ ํจํ ํ ํฐ์ด๋ผ๋ฉด ์์ฒญ์ ์๋ต
๐ ํ ํฐ์ ์ ์งํ๋ ์์น
secure httpOnly ์ฟ ํค, ๋ก์ปฌ ์คํ ๋ฆฌ์ง, ์ฟ ํค ๋ฑ์ด ์กด์ฌํ๋ค
- ์ฟ ํค: XSS(Cross-site scripting)์ CSRF(Cross-site request forgery): ๋ค๋ฅธ ๋๋ฉ์ธ์์ ์ฐ๋ฆฌ ๋๋ฉ์ธ์ผ๋ก API Call์ ๋ ๋ฆฌ๋ ๊ณต๊ฒฉ์ ์ทจ์ฝ
- localStorage: ๊ณต๊ฒฉ์๊ฐ ํด๋ผ์ด์ธํธ ๋ธ๋ผ์ฐ์ ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฝ์ ํด ๊ณต๊ฒฉํ๋ XSS(Cross-site scripting)์ ์ทจ์ฝ
- secure httpOnly ์ฟ ํค: CSRF ๊ณต๊ฒฉ์ ์ทจ์ฝํ๋ค.
๐ ๊ตฌํ
ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ํด๋ผ์ด์ธํธ์ ์ฟ ํค์ JWT๋ฅผ ์ ์ฅํ๋๋ก ํ๋ ๋ฐฉ์์ ์ฌ์ฉํ๋ค.
ํด๋ผ์ด์ธํธ์์ ๋ก๊ทธ์ธ ์์ฒญ์ ๋ณด๋ด๊ฒ ๋๋ฉด ์๋ฒ์์๋ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๊ณ ๋ฑ๋ก๋ ์ ์ ์ธ ๊ฒฝ์ฐ ๋๋ Oauth๋ฅผ ํตํด ์๋ต๋ฐ์ ๊ฐ์ด ์ ์์ ์ผ ๋ JWT๋ฅผ resopnse์ ํจ๊ป ํด๋ผ์ด์ธํธ์๊ฒ ๋ณด๋ด์ค๋ค.
const createToken = (accessToken: string, expirePeriod: string) => {
return jwt.sign({ accessToken }, process.env.JWT_SECRET_KEY, {
expiresIn: expirePeriod,
});
};
const decodeToken = (accessToken: string) => {
return jwt.verify(accessToken, process.env.JWT_SECRET_KEY || '');
};
const { data: requestToken } = await axios.post(baseURI, body, {
headers: { Accept: 'application/json' },
});
const { access_token: accessToken } = requestToken;
res.cookie('accessToken', createToken(accessToken, '7d'), {
maxAge: 1000 * 60 * 60 * 24 * 7,
httpOnly: true,
});
์ ์ฝ๋๋ Oauth๋ฅผ ํ์ฉํ๋ ์ฝ๋์ธ๋ฐ baseURI๋ก post ์์ฒญ์ ํ๊ณ ๋ฐ์์จ access token์ JWT์ payload๋ก ๋ฃ์ด์ค ์ฝ๋์ด๋ค.
ํด๋ผ์ด์ธํธ์์๋ ๋ก๊ทธ์ธ ์ดํ ๋ก๊ทธ์ธ ๊ถํ์ด ํ์ํ ์ด๋ ํ ์์ฒญ์ ํ ๋๋ง๋ค ์๋ฒ ์ธก์์ JWT๋ฅผ ํตํ ๊ฒ์ฆ์ด ์ด๋ฃจ์ด์ง๊ณ ์ฌ๋ฐ๋ฅผ ๊ฒฝ์ฐ์๋ง ์ ๋๋ก ๋ ์๋ต์ ํด๋ผ์ด์ธํธ ์ธก์ผ๋ก ์ ์กํด์ฃผ๋๋ก ํ๋ค. ๊ฒ์ฆ์ด ์ด๋ฃจ์ด์ง๋ ๊ณผ์ ์ JWT์ payload๋ฅผ ํด์ํ๊ณ ์ด๋ฅผ ํตํด ์ฌ๋ฐ๋ฅธ ์ ์ ์ธ์ง ํ์ธํ์๋๋ฐ ์ธ์ ์ ์ฌ์ฉํด์ ์๋ฒ์ธก์์ ๊ฒ์ฆํ ์ ์๋ ๊ฐ์ ๊ฐ์ง๊ณ ์๋๋ค๋ฉด ํจ์ฌ ํจ์จ์ ์ธ ์ฝ๋๊ฐ ๋ ๊ฒ ๊ฐ๋ค.
'Web' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Web] CORS (0) | 2023.05.18 |
---|---|
[Web] Socket, WebSocket (์์ผ๊ณผ ์น์์ผ) (2) | 2023.03.14 |
[Web] ๋ธ๋ผ์ฐ์ ์์ ๋๋ฉ์ธ์ ์ ๋ ฅํ๋ฉด ์ผ์ด๋๋ ์ผ (0) | 2023.03.02 |
[Web] Webpack (0) | 2023.02.22 |