์ธ์ฆ๊ณผ ์ธ๊ฐ๋ ๋ฌด์์ผ๊น?
์ธ์ฆ๊ณผ ์ธ๊ฐ

์ธ์ฆ(Authentication): ํด๋น ์ ์ ๊ฐ ์ค์ ์ ์ ์ธ์ง ์ธ์ฆํ๋ ๊ฐ๋
์ธ๊ฐ(Authorization): ํด๋น ์ ์ ๊ฐ ํน์ ๋ฆฌ์์ค์ ์ ๊ทผ์ด ๊ฐ๋ฅํ์ง ํ๊ฐ๋ฅผ ํ์ธํ๋ ๊ฐ๋ ์ผ๋ก, ๊ด๋ฆฌ์ ํ์ด์ง-๊ด๋ฆฌ์ ๊ถํ ๊ฐ์ ๊ฒ์ด ์์
์ฝ๊ฒ ์ค๋ช ํ๋ฉด, ๋ก๊ทธ์ธ์ ์ธ์ฆ์ ํ ๋(๋น๋ฐ๋ฒํธ ์ ๋ ฅํ๊ณ ์ ์ถํ ๋)์ด๊ณ , ํ์/๋นํ์ ์ฌ๋ถ์ ๋ฐ๋ผ ๋ค๋ฅธ ๊ถํ์ ๋ฐ๋ ๊ฒ์ ์ธ๊ฐ์
์น ์ ํ๋ฆฌ์ผ์ด์ ์ธ์ฆ์ ํน์์ฑ

HTTP๋ ๋น์ฐ๊ฒฐ์ฑ(Connectionless)์ ๋ฌด์ํ(Stateless)๋ก ์ด๋ฃจ์ด์ ธ ์์ → ์๋ฒ์ ๋น์ฉ์ ์ค์ด๊ธฐ ์ํด
- ๋น์ฐ๊ฒฐ์ฑ(Connectionless): ์๋ฒ์ ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐ์ด ๋์ด ์์ง ์์์ ์๋ฏธ
์ฆ, ์๋ฒ๋ ํ๋์ ์์ฒญ์ ๋ํด์ ์๋ต์ ๋ด๋ณด๋ด๊ณ ์ฐ๊ฒฐ์ ๋์ - ๋ฌด์ํ(Stateless): ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์ ์ํ๋ฅผ ์ ์ฅํ๊ณ ์์ง ์์์ ์๋ฏธ
์ฆ, ์๋ฒ๋ ํด๋ผ์ด์ธํธ๊ฐ ์ง์ ์ ํน์ ๊ทธ ์ ์ ์ด๋ ํ ์์ฒญ์ ๋ณด๋๋์ง ๊ด์ฌ๋ ์๊ณ ์ ํ ์ ์ ์์
HTTP๊ฐ ์ด๋ฌํ ํน์ง์ ๊ฐ์ง๊ณ ์์ง๋ง, ์ฐ๋ฆฌ๊ฐ ์ธํฐ๋ท์ ์ฌ์ฉํ ๋๋ ์ด์ ์ ๋ณด๋ค์ด ์ ์๋ ๊ฒ์ฒ๋ผ ๋ญ๊ฐ ์ฐ์์ฑ ์๊ฒ ์ฌ์ฉํด์ด.
์๋ฅผ ๋ค์ด, ์ ์ ์ธ์ฆ๋ ํ๋์ ์ํ ๊ฐ์ธ๋ฐ ๊ทธ ์ ๋ณด๋ฅผ ์ด๋ป๊ฒ ์ ์ง์ํค๊ณ ์๋ ๊ฒ์ธ๊ฐ?
์ธ์ฆ์ ๋ฐฉ์
์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ํตํด ์ธ์ฆ์ ์ฒ๋ฆฌํจ
1. ์ฟ ํค-์ธ์ ๋ฐฉ์์ ์ธ์ฆ
์ฟ ํค(Cookie): ํ ํฐ ์ ์ฅ์
์ธ์ : ์ธ์ฆ ์ ๋ณด
- ์ฟ ํค-์ธ์ ๋ฐฉ์์ ์๋ฒ๊ฐ 'ํน์ ์ ์ ๊ฐ ๋ก๊ทธ์ธ์ด ๋์๋ค'๋ผ๋ ์ํ๋ฅผ ์ ์ฅํ๋ ๋ฐฉ์ ์ค ํ๋
- ์ธ์ฆ๊ณผ ๊ด๋ จ๋ ์ฝ๊ฐ์ ์ ๋ณด๋ฅผ ์๋ฒ๊ฐ ๊ฐ์ง๊ณ ์์. ์ฆ, ์ ์ ์ ์ด์ ์ํ ์ ๋ถ๋ ์๋๋๋ผ๋ ์ธ์ฆํ๊ณ ๊ด๋ จ๋ ์ต์ํ์ ์ ๋ณด๋ฅผ ์ ์ฅํด์ ๋ก๊ทธ์ธ์ ์ ์ง์ํด

1) ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ ์งํ
2) ์๋ฒ์์ ํ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋์กฐํด์ ์ฌ์ฉ์ ํ์ธํจ
3) ์ค์ ์ ์ ํ ์ด๋ธ์ ์ ๋ณด์ ์ผ์นํ๋ค๋ฉด ์ธ์ฆ์ด ํต๊ณผ๋์๋ค๊ณ ๋ณด๊ณ , ์ธ์ ์ ์ฅ์์ ํด๋น ์ ์ ๊ฐ ๋ก๊ทธ์ธ์ด ๋์๋ค๋ผ๋ ์ ๋ณด๋ฅผ ์ ์ฅํจ
4) ์ธ์ ์ ์ฅ์์์๋ ์ ์ ์ ์ ๋ณด์๋ ๊ด๋ จ์๋ ๋์์ธ ์ธ์ ID๋ฅผ ๋ฐ๊ธ
5) ์๋ฒ๋ ๋ฐ๊ธ๋ฐ์ ์ธ์ ID๋ฅผ ์๋ต์ ๋ด์์ ์ฌ์ฉ์์๊ฒ ๋ณด๋
6) ํด๋ผ์ด์ธํธ(์ฌ์ฉ์)๋ ์ธ์ ID๋ฅผ ๋ธ๋ผ์ฐ์ ๋ด์ ์ฟ ํค(Cookie)๋ผ๋ ์ ์ฅ์์ ๋ณด๊ดํ๊ณ ์๋ค๊ฐ, ์์ฒญ์ ํ ๋๋ง๋ค ํด๋น ์ธ์ ID๋ฅผ ๊ฐ์ด ๋ณด๋ → ์ฃผ๋ก HTTP Header์ ๋ด๊ฒจ์ ๋ณด๋ด์ง
7) ์๋ฒ๋ ํด๋ผ์ด์ธํธ ์์ฒญ์์ ์ฟ ํค ๋ฐ๊ฒฌํ๋ฉด ์๋ฒ์ ์ธ์ ์ ์ฅ์์ ์ ์ฅ๋์ด ์๋ ์ ๋ณด๋ ๋น๊ตํด์ ๊ฒ์ฆํจ
8) ์ ์ ์ ๋ณด๋ฅผ ์ ๋๋ก ๋ฐ์์๋ค๋ฉด ์ฌ์ฉ์๋ ๋ก๊ทธ์ธ์ด ๋ ์ฌ์ฉ์๋ผ๊ณ ์ธ์ฆ์ด ๋์ด, ๊ทธ ์ดํ์๋ ๋ก๊ทธ์ธ์ด ๋ ์ ์ ์ ๋ฐ๋ฅธ ์๋ต์ ๋ณด๋ด์ค
2. JWT ๊ธฐ๋ฐ ์ธ์ฆ
JWT(JSON Web Token)์ JSON ํ์์ผ๋ก ์๊ธด ์น์์ ์ฌ์ฉํ๋ ํ ํฐ
- JWT๋ ์ธ์ฆ์ ํ์ํ ์ ๋ณด๋ค์ ์ํธํ์์ผ ๋์
- JWT ๊ธฐ๋ฐ ์ธ์ฆ์ ์ฟ ํค-์ธ์ ๋ฐฉ์๊ณผ ์ ์ฌํ๊ฒ JWT๋ฅผ HTTP Header์ ์ค์ด์ ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ๋ฅผ ์๋ณํ ์ ์์

1) ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ ์งํ
2) ์๋ฒ์์ ํ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋์กฐํด์ ์ฌ์ฉ์ ํ์ธํจ
3) ์ค์ ์ ์ ํ ์ด๋ธ์ ์ ๋ณด์ ์ผ์นํ๋ค๋ฉด ์ธ์ฆ์ด ํต๊ณผ๋์๋ค๊ณ ๋ณด๊ณ , ์ ์ ์ ๋ํ ์ ๋ณด๋ฅผ JWT๋ก ์ํธํํจ
4) ์ํธํ๋ ํ ํฐ(Access Token)์ธ JWT๋ฅผ ์๋ต์ ์ค์ด์ ๋ด๋ณด๋(Body ํน์ Header)
5) ํด๋ผ์ด์ธํธ(์ฌ์ฉ์)๋ ํ ํฐ์ ์ฟ ํค ์ ์ฅ์ ํน์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ ์ฅํด ๋์๋ค๊ฐ, ์์ฒญ์ ํ ๋๋ง๋ค ํด๋นํ๋ JWT ํ ํฐ์ ๊ฐ์ด ๋ณด๋
6) ์๋ฒ๋ ์์ฑํด๋์ ๊ฒ์ฆ ๋ก์ง์ ํตํด ํ ํฐ์ ๊ฒ์ฆ ํ, ์ ์ ์ ๋ฐ๋ฅธ ์๋ต์ ๋ด๋ณด๋
๏นก์ฟ ํค-์ธ์ ๋ฐฉ์์์๋ ๋ฐ๊ธํ ์ธ์ ์ ์ ์ฅํ ๊ณต๊ฐ์ด ํ์ํ์ง๋ง, JWT๋ ๋ฐ๋ก ํ ํฐ์ ์ ์ฅํ ํ์๊ฐ ์์
๋ฐ๋ผ์ JWT๊ฐ ํจ์ฌ ์ฟ ํค-์ธ์ ๋ฐฉ์๋ณด๋ค ์๋ฒ์ ๋ถ๋ด์ด ๋ํ๊ธฐ ๋๋ฌธ์ ํจ์ฌ ํจ์จ์ ์
์ฟ ํค์ ์ธ์ ์ด๋ ๋ฌด์์ผ๊น?
์ฟ ํค์ ์ธ์
์ฟ ํค์ ์ธ์ ๋ชจ๋ HTTP์ ์ํ ์ ๋ณด๋ฅผ ์ ์ง(Stateful)ํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ ๊ฒ์ผ๋ก, ์ฟ ํค์ ์ธ์ ์ ํตํด ์๋ฒ์์๋ ํด๋ผ์ด์ธํธ ๋ณ๋ก ์ธ์ฆ ๋ฐ ์ธ๊ฐ๋ฅผ ํ ์ ์์
์ฟ ํค(Cookie)
ํด๋ผ์ด์ธํธ์ ์ ์ฅ๋ ๋ชฉ์ ์ผ๋ก ์์ฑํ ์์ ์ ๋ณด๋ฅผ ๋ด์ ํ์ผ
- ํฌ๋กฌ ๋ธ๋ผ์ฐ์ ๊ธฐ์ค > ๊ฐ๋ฐ์ ๋๊ตฌ > Application > Storage > Cookies์ ๋๋ฉ์ธ ๋ณ๋ก ์ ์ฅ๋์ด ์์

[๊ตฌ์ฑ ์์]
- ์ด๋ฆ(Name): ์ฟ ํค๋ฅผ ๊ตฌ๋ณํ๋๋ฐ ์ฌ์ฉ๋๋ Key
- ๊ฐ(Value): ์ฟ ํค์ ๊ฐ
- ๋๋ฉ์ธ(Domain): ์ฟ ํค๊ฐ ์ ์ฅ๋ ๋๋ฉ์ธ์ผ๋ก, ์ฌ๋ฌ ๊ฒฝ๋ก์ ๋๋ ์ ์ฟ ํค๊ฐ ์ ์ฅ๋ ์๋ ์์
- ๊ฒฝ๋ก(Path): 'Domain Local/API' ์ด๋ฐ์์ผ๋ก ๊ตฌ๋ถ ๊ฐ๋ฅ
- Expires/Max-Age: ๋ง๋ฃ ๊ธฐํ์ผ๋ก, ์ฟ ํค๋ฅผ ๋ง๋ค ๋ ๋ง๋ฃ ๊ธฐํ์ ์ค ์ ์์. ๋ง๋ฃ ๊ธฐํ์ด ์ง๋๋ฉด ์ญ์ ๋จ
- ํฌ๊ธฐ(Size)
- HttpOnly
- Secure
- ๋ฑ๋ฑ
์ธ์ (Session)
์๋ฒ์์ ์ผ์ ์๊ฐ ๋์ ํด๋ผ์ด์ธํธ ์ํ๋ฅผ ์ ์งํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ ๊ฒ
- ์๋ฒ์์ ํด๋ผ์ด์ธํธ ๋ณ๋ก ์ ์ผ๋ฌด์ดํ '์ธ์ ID'๋ฅผ ๋ถ์ฌํ ํ ํด๋ผ์ด์ธํธ ๋ณ ํ์ํ ์ ๋ณด๋ฅผ ์๋ฒ์ ์ ์ฅํจ
- ์๋ฒ์์ ์์ฑํ '์ธ์ ID'๋ ํด๋ผ์ด์ธํธ์ ์ฟ ํค๊ฐ('์ธ์ ์ฟ ํค'๋ผ๊ณ ๋ถ๋ฆ)์ผ๋ก ์ ์ฅ๋์ด ํด๋ผ์ด์ธํธ ์๋ณ์ ์ฌ์ฉ๋จ
[์ธ์ ๋์ ๋ฐฉ์]

1) ํด๋ผ์ด์ธํธ๊ฐ ์ฒซ ๋ฒ์งธ ์์ฒญ์ ํ๋ฉด, ์๋ฒ์์ ์ธ์ ID๋ฅผ ์์ฑํด์ ์ฟ ํค์ ๋ด์์ ์๋ต Header์ ์ ๋ฌํจ
2) ์ธ์ ID๊ฐ ํด๋ผ์ด์ธํธ์ ์ฟ ํค ์ ์ฅ์์ ์ ์ฅ๋จ(Set-Cookie: ์ธ์ ID)
3) ์ด์ด์ ํด๋ผ์ด์ธํธ๊ฐ ๋ ๋ฒ์งธ ์์ฒญ์ ๋ณด๋ผ ๋, ํด๋นํ๋ ์ธ์ ID ๊ฐ์ ๊ฐ์ด ํฌํจํด์ ๋ณด๋
4) ์๋ฒ๋ ํด๋นํ๋ ์ธ์ ID๋ฅผ ํ์ธํ๊ณ , ๊ฐ์ ๋ธ๋ผ์ฐ์ ์์ ์์ฒญํ HTTP ์์ฒญ(=๊ฐ์ ํด๋ผ์ด์ธํธ์ ์์ฒญ)์์ ํ์ธํจ
์ฟ ํค์ ์ธ์ ๋น๊ต
| ์ฟ ํค(Cookie) | ์ธ์ (Session) | |
| ์ค๋ช | ํด๋ผ์ด์ธํธ์ ์ ์ฅ๋ ๋ชฉ์ ์ผ๋ก ์์ฑํ ์์ ์ ๋ณด๋ฅผ ๋ด์ ํ์ผ | ์๋ฒ์์ ์ผ์ ์๊ฐ ๋์ ํด๋ผ์ด์ธํธ ์ํ๋ฅผ ์ ์งํ๊ธฐ ์ํด ์ฌ์ฉ |
| ์ ์ฅ ์์น | ํด๋ผ์ด์ธํธ (์น ๋ธ๋ผ์ฐ์ ) | ์น ์๋ฒ |
| ์ฌ์ฉ ์ | ์ฌ์ดํธ ํ์ ์ "์ค๋ ๋ค์๋ณด์ง ์๊ธฐ" ์ ๋ณด ์ ์ฅ | ๋ก๊ทธ์ธ ์ ๋ณด ์ ์ฅ |
| ๋ง๋ฃ ์์ | ์ฟ ํค ์ ์ฅ ์ ๋ง๋ฃ์ผ์ ์ค์ ๊ฐ๋ฅ (๋ธ๋ผ์ฐ์ ์ข ๋ฃ์๋ ์ ์ง ๊ฐ๋ฅ) |
๋ค์ ์กฐ๊ฑด ์ค ํ๋๊ฐ ๋ง์กฑ๋ ๊ฒฝ์ฐ ๋ง๋ฃ๋จ - ๋ธ๋ผ์ฐ์ ์ข ๋ฃ ์๊น์ง - ํด๋ผ์ด์ธํธ ๋ก๊ทธ์์ ์๊น์ง - ์๋ฒ์ ์ค์ ํ ์ ์ง๊ธฐ๊ฐ๊น์ง ํด๋น ํด๋ผ์ด์ธํธ์ ์ฌ์์ฒญ์ด ์๋ ๊ฒฝ์ฐ |
| ์ฉ๋ ์ ํ | ๋ธ๋ผ์ฐ์ ๋ณ๋ก ๋ค๋ฆ(ํฌ๋กฌ ๊ธฐ์ค) - ํ๋์ ๋๋ฉ์ธ ๋น 180๊ฐ - ํ๋์ ์ฟ ํค ๋น 4KB(=4096byte) |
๊ฐ์ ์ ํ ์์ (๋จ, ์ธ์ ์ ์ฅ์ ํฌ๊ธฐ ์ด์ ์ ์ฅ ๋ถ๊ฐ๋ฅ) |
| ๋ณด์ | ์ทจ์ฝ (ํด๋ผ์ด์ธํธ์์ ์ฟ ํค ์ ๋ณด๋ฅผ ์ฝ๊ฒ ๋ณ๊ฒฝ, ์ญ์ ๋ฐ ๊ฐ๋ก์ฑ๊ธฐ ๋นํ ์ ์์) |
๋น๊ต์ ์์ (์๋ฒ์ ์ ์ฅ๋๊ธฐ ๋๋ฌธ์ ์๋์ ์ผ๋ก ์์ ) |
Spring์์ ์ฟ ํค-์ธ์ ๋ค๋ค๋ณด๊ธฐ
์ฟ ํค ์์ฑํ๊ณ ๊ฐ์ ธ์ค๊ธฐ
package com.sparta.springauth.auth;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
@RestController
@RequestMapping("/api")
public class AuthController {
public static final String AUTHORIZATION_HEADER = "Authorization";
// ์ฟ ํค ์์ฑํ๋ ๋ฉ์๋
@GetMapping("/create-cookie")
public String createCookie(HttpServletResponse res) {
addCookie("Robbie Auth", res);
return "createCookie";
}
// ์ฟ ํค ๊ฐ์ ธ์ค๋ ๋ฉ์๋
@GetMapping("/get-cookie")
public String getCookie(@CookieValue(AUTHORIZATION_HEADER) String value) {
System.out.println("value = " + value);
return "getCookie : " + value;
}
public static void addCookie(String cookieValue, HttpServletResponse res) {
try {
cookieValue = URLEncoder.encode(cookieValue, "utf-8").replaceAll("\\+", "%20"); // Cookie Value ์๋ ๊ณต๋ฐฑ์ด ๋ถ๊ฐ๋ฅํด์ encoding ์งํ
// Spring์์ ์ฟ ํค ํด๋์ค ์ ๊ณตํจ
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, cookieValue); // Name-Value
cookie.setPath("/");
cookie.setMaxAge(30 * 60);
// Response ๊ฐ์ฒด์ Cookie ์ถ๊ฐ
res.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage());
}
}
}



์ธ์ ์์ฑํ๊ณ ๊ฐ์ ธ์ค๊ธฐ
Servlet์์ ์ ์ผ๋ฌด์ดํ ์ธ์ ID๋ฅผ ๊ฐํธํ๊ฒ ๋ง๋ค์ด์ฃผ๋ HttpSession ์ ๊ณตํด์ค
@GetMapping("/create-session")
public String createSession(HttpServletRequest req) {
// ์ธ์
์ด ์กด์ฌํ ๊ฒฝ์ฐ ์ธ์
๋ฐํ, ์์ ๊ฒฝ์ฐ ์๋ก์ด ์ธ์
์ ์์ฑํ ํ ๋ฐํ
HttpSession session = req.getSession(true);
// ์ธ์
์ ์ ์ฅ๋ ์ ๋ณด Name - Value ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
session.setAttribute(AUTHORIZATION_HEADER, "Robbie Auth");
return "createSession";
}
@GetMapping("/get-session")
public String getSession(HttpServletRequest req) {
// ์ธ์
์ด ์กด์ฌํ ๊ฒฝ์ฐ ์ธ์
๋ฐํ, ์์ ๊ฒฝ์ฐ null ๋ฐํ
HttpSession session = req.getSession(false);
String value = (String) session.getAttribute(AUTHORIZATION_HEADER); // ๊ฐ์ ธ์จ ์ธ์
์ ์ ์ฅ๋ Value ๋ฅผ Name ์ ์ฌ์ฉํ์ฌ ๊ฐ์ ธ์ต๋๋ค.
System.out.println("value = " + value);
return "getSession : " + value;
}



JWT๋ ๋ฌด์์ผ๊น?
JWT(JSON Web Token)๋ JSON ํฌ๋งท์ ์ด์ฉํด ์ฌ์ฉ์์ ๋ํ ์์ฑ์ ์ ์ฅํ๋ Claim ๊ธฐ๋ฐ์ Web Token์ผ๋ก, ์ฆ ํ ํฐ์ ํ ์ข ๋ฅ์
- ์ผ๋ฐ์ ์ผ๋ก ์ฟ ํค ์ ์ฅ์๋ฅผ ์ฌ์ฉํด JWT๋ฅผ ์ ์ฅํจ
JWT ์ฌ์ฉ ์ด์


- ์๋ฒ๊ฐ 1๋์ธ ๊ฒฝ์ฐ์๋ Session1์ด ๋ชจ๋ Client์ ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ์์ ํ๊ณ ์์
- ์๋ฒ๊ฐ 2๋ ์ด์์ธ ๊ฒฝ์ฐ(์๋ฒ์ ๋์ฉ๋ ํธ๋ํฝ ์ฒ๋ฆฌ๋ฅผ ์ํด ์๋ฒ 2๋ ์ด์ ์ด์์ด ํ์ํ ์ ์์),
- Session ๋ง๋ค ๋ค๋ฅธ Client ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์์ ์ ์์
- Session1: Client1, Client2, Client3
- Session2: Client4
- Session3: Client5, Client6 - ๋ก๋๋ฐธ๋ฐ์ค๋ ์๋ฒ์ ๋๋ค์ผ๋ก ์์ฒญ์ ์ ๋ฌํจ
- ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋ง์ฝ Client1์ ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์์ง ์์ Server2๋ Server3์ API ์์ฒญ์ ํ๊ฒ ๋๋ฉด ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์
- Session ๋ง๋ค ๋ค๋ฅธ Client ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์์ ์ ์์
์์ ๊ฐ์ ๋ฌธ์ ์ ํด๊ฒฐ๋ฐฉ๋ฒ
1) Sticky Session: Client๋ง๋ค ์์ฒญ Server๋ฅผ ๊ณ ์ ์ํด
2) ์ธ์ ์ ์ฅ์ ์์ฑํด ๋ชจ๋ ์ธ์ ์ ์ฅํจ
์ธ์ ์ ์ฅ์ ์์ฑ: Session Storage๊ฐ ๋ชจ๋ Client์ ๋ก๊ทธ์ธ ์ ๋ณด ์์ ํ๊ณ ์๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ์๋ฒ์์ ๋ชจ๋ Client์ API ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์์

3) JWT ์ฌ์ฉ
๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ Server์ ์ ์ฅํ์ง ์๊ณ , Client์ ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ JWT๋ก ์ํธํํด ์ ์ฅ ํ JWT ํตํด ์ธ์ฆ/์ธ๊ฐ

- ๋ชจ๋ ์๋ฒ์์ ๋์ผํ Secret Key ์์ ํจ
- Secret Key๋ฅผ ํตํ ์ํธํ / ์์กฐ ๊ฒ์ฆ(๋ณตํธํ ์)

JWT ์ฅ/๋จ์
์ฅ์
- ๋์ ์ ์์๊ฐ ๋ง์ ๋ ์๋ฒ ์ธก ๋ถํ ๋ฎ์ถค → ์ธ์ ์ ์ฅ์์์ด ์๋ฒ ์์ฒด์์ ๊ฒ์ฆ ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์
- Client, Server๊ฐ ๋ค๋ฅธ ๋๋ฉ์ธ์ ์ฌ์ฉํ ๋ ex) ์นด์นด์ค OAuth2 ๋ก๊ทธ์ธ ์ JWT Token ์ฌ์ฉ
๋จ์
- ๊ตฌํ์ ๋ณต์ก๋ ์ฆ๊ฐ
- JWT์ ๋ด๋ ๋ด์ฉ์ด ์ปค์ง์๋ก ๋คํธ์ํฌ ๋น์ฉ ์ฆ๊ฐํจ(ํด๋ผ์ด์ธํธ → ์๋ฒ)
- JWT ์์ ๋ฌด๋ถ๋ณํ ์ ๋ณด๋ฅผ ๋ฃ๋ ๊ฒฝ์ฐ JWT ๊ธธ์ด๊ฐ ๊ธธ์ด์ง๊ณ ,
- ๊ทธ JWT๊ฐ HTTP ํ๋กํ ์ฝ์ ๋ด๊ฒจ์ ๋ค์ด์ฌ ๋, HTTP ํ๋กํ ์ฝ์ด ๋ฌด๊ฒ๊ณ ์ฉ๋์ด ํด์๋ก ๋คํธ์ํฌ ๋น์ฉ์ด ์ฆ๊ฐํจ
- ๊ธฐ ์์ฑ๋ JWT๋ฅผ ์ผ๋ถ๋ง ๋ง๋ฃ์ํฌ ๋ฐฉ๋ฒ์ด ์์
- ์ฟ ํค ์ ์ฅ์์ ์ ์ฅ๋ JWT๋ฅผ ์๋ฒ์์ ๋ง๋ฃ์ํฌ ๋ฐฉ๋ฒ์ด ์์
- ๋์ , ์ฟ ํค ์ ์ฅ์์ ์ ์ฅํ ๋ ์ฟ ํค ์์ฒด ํน์ JWT ์์ฒด์ ๋ง๋ฃ ๊ธฐํ์ ์ค ์ ์์
- Secret Key ์ ์ถ ์ JWT ์กฐ์ ๊ฐ๋ฅ
- ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ JWT์ ๋น๋ฐ๋ฒํธ์ ๊ฐ์ ๋ฏผ๊ฐํ ์ ๋ณด ๋ฃ์ผ๋ฉด ์ ๋จ!
JWT ์ฌ์ฉ ํ๋ฆ
1. Client๊ฐ username, password๋ก ๋ก๊ทธ์ธ ์ฑ๊ณต ์
1-1) ์๋ฒ์์ "๋ก๊ทธ์ธ ์ ๋ณด" → JWT๋ก ์ํธํ(Secret Key ์ฌ์ฉ)

1-2) ์๋ฒ์์ ์ง์ ์ฟ ํค๋ฅผ ์์ฑํด JWT๋ฅผ ๋ด์ Client ์๋ต์ ์ ๋ฌ
- JWT ์ ๋ฌ ๋ฐฉ๋ฒ์ ๊ฐ๋ฐ์๊ฐ ์ ํจ
// ex) ์๋ต Header์ ์๋ ํํ๋ก JWT ์ ๋ฌ
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token);
cookie.setPath("/");
// Response ๊ฐ์ฒด์ Cookie ์ถ๊ฐ
res.addCookie(cookie);

1-3) ๋ธ๋ผ์ฐ์ ์ฟ ํค ์ ์ฅ์์ ์๋์ผ๋ก JWT ์ ์ฅ๋จ
2. Client์์ JWT ํตํด ์ธ์ฆํ๋ ๋ฐฉ๋ฒ
2-1) ์๋ฒ์์ API ์์ฒญ ์๋ง๋ค ์ฟ ํค์ ํฌํจ๋ JWT๋ฅผ ์ฐพ์์ ์ฌ์ฉ
// ์ฟ ํค ์ฐพ๋ ์ฝ๋
// HttpServletRequest์์ Cookie Value: JWT ๊ฐ์ ธ์ค๊ธฐ
public String getTokenFromRequest(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if(cookies != null) {
for(Cookie cookie : cookies) {
if(cookie.getName().equals(AUTHORIZATION_HEADER)) {
try {
return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode ๋์ด ๋์ด๊ฐ Value ๋ค์ Decode
}catch (UnsupportedEncodingException e){
return null;
}
}
}
}
return null;
}
- ์ฟ ํค์ ๋ด๊ธด ์ ๋ณด๊ฐ ์ฌ๋ฌ ๊ฐ์ผ ์ ์๊ธฐ ๋๋ฌธ์ ๊ทธ ์ค ์ด๋ฆ์ด JWT๊ฐ ๋ด๊ธด ์ฟ ํค์ ์ด๋ฆ๊ณผ ๋์ผํ์ง ํ์ธํด JWT๋ฅผ ๊ฐ์ ธ์ด
2-2) Server์์ ๊ฒ์ฆ ํ์ธ ์ ์ฐจ
- Client๊ฐ ์ ๋ฌํ JWT ์์กฐ ์ฌ๋ถ ๊ฒ์ฆ(Secret Key ์ฌ์ฉ)
- JWT ์ ํจ๊ธฐ๊ฐ์ด ์ง๋์ง ์์๋์ง ๊ฒ์ฆ
- ๊ฒ์ฆ ์ฑ๊ณต์, JWT์์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ ํ์ธ
JWT ๊ตฌ์กฐ
- JWT๋ ๋๊ตฌ๋ ํ๋ฌธ์ผ๋ก ๋ณตํธํ ๊ฐ๋ฅํจ
- ํ์ง๋ง Secret Key๊ฐ ์์ผ๋ฉด JWT ์์ ๋ถ๊ฐ๋ฅํจ(์ฆ, JWT๋ Read Only ๋ฐ์ดํฐ์)
- jwt.io

1. Header
- ์ด๋ค ์๊ณ ๋ฆฌ์ฆ ์ฌ์ฉํ๋์ง ex) HS256
- ํ์ ex) JWT
2. Payload
- ๋ฐ์ดํฐ ์ ๋ณด
- JSON ํํ๋ก ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋์ด ์์
3. JWT Signature Verification
- Secret Key๋ฅผ ๋ฃ์
JWT ๋ค๋ฃจ๊ธฐ
- JWT๋ฅผ Spring์์ ์กฐ์ํ๋ ค๋ฉด, ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐํด์ผ ํจ → build.gradle ๋ด dependencies์ ์๋ ์ฝ๋ ์ถ๊ฐ
// JWT
compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
- application.properties์ Secret Key ์ถ๊ฐ
// Base64๋ก ์ธ์ฝ๋ฉ๋์ด ์๋ secret key
jwt.secret.key=7Iqk7YyM66W07YOA7L2U65Sp7YG065+9U3ByaW5n6rCV7J2Y7Yqc7YSw7LWc7JuQ67mI7J6F64uI64ukLg==
JWT Util ํด๋์ค ์์ฑ
JWT ์ ํธ์ JWT์ ๊ด๋ จ๋ ๊ธฐ๋ฅ๋ค์ ๊ฐ์ง ํด๋์ค
Util(์ ํธ) ํด๋์ค
ํน์ ํ ๋งค๊ฐ๋ณ์ ํน์ ํ๋ผ๋ฏธํฐ์ ๋ํ ์ด๋ ํ ์์ ์ ์ํํ๋ ๋ฉ์๋๋ค์ด ์กด์ฌํ๋ ํด๋์ค
์ฆ, ๋ค๋ฅธ ๊ฐ์ฒด์ ์์กดํ์ง ์๊ณ ํ๋์ ๋ชจ๋๋ก์จ ๋์ํ๋ ํด๋์ค
ex) 3,000์ด๋ผ๊ณ ํ์ ๋ ์ ํน์ ๋ฌ๋ฌ๋ฅผ ์ถ๊ฐ์ ์ผ๋ก ๋ถ์ฌ์ผํ๋ ๋ฌธ์์ด ์กฐ์ํ๋ ์ ํธ ํด๋์ค
ex) ๋ ์ง๋ฅผ ์กฐ์ํ๋ ์ ํธ ํด๋์ค
ํ ํฐ ์์ฑ์ ํ์ํ JWT ๋ฐ์ดํฐ
// Header KEY ๊ฐ
public static final String AUTHORIZATION_HEADER = "Authorization";
// ์ฌ์ฉ์ ๊ถํ ๊ฐ์ KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token ์๋ณ์
// ํ ํฐ ์์ ๋ถ์ผ ๊ท์น
public static final String BEARER_PREFIX = "Bearer ";
// ํ ํฐ ๋ง๋ฃ์๊ฐ
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60๋ถ
@Value("${jwt.secret.key}") // Base64 Encode ํ SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// ๋ก๊น
: ์ ํ๋ฆฌ์ผ์ด์
์ด ๋์์ ํ๋ ๋์์ ํ๋ก์ ํธ์ ์ํ๋ ์ด๋ ํ ๋์ ์ ๋ณด๋ฅผ ์๊ฐ ์์ผ๋ก ์
๋ ฅํ๋ ๊ฒ
public static final Logger logger = LoggerFactory.getLogger("JWT ๊ด๋ จ ๋ก๊ทธ");
// @PostConstruct๋ ๋ฑ ํ ๋ฒ๋ง ๋ฐ์ ์ค๋ฉด ๋๋ ๊ฐ์ ์ฌ์ฉํ ๋๋ง๋ค ์์ฒญ์ ์๋ก ํธ์ถํ๋ ์ค์๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ฌ์ฉ
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
์ฌ์ฉ์ ๊ถํ์ ๊ด๋ฆฌํ๋ Enum Class ์์ฑ
package com.sparta.springauth.entity;
public enum UserRoleEnum {
USER(Authority.USER), // ์ฌ์ฉ์ ๊ถํ
ADMIN(Authority.ADMIN); // ๊ด๋ฆฌ์ ๊ถํ
private final String authority;
UserRoleEnum(String authority) {
this.authority = authority;
}
public String getAuthority() {
return this.authority;
}
public static class Authority {
public static final String USER = "ROLE_USER";
public static final String ADMIN = "ROLE_ADMIN";
}
}
JWT ์์ฑ
[JWT ์์ฑ ํ ๋ฐํํ๋ ๋ฐฉ๋ฒ 2๊ฐ์ง]
1. Response Header์ JWT ํ ํฐ ๋ฌ์์ ์๋ต
- Cookie ์๊ฐ๋ณด๋ค ์ฝ๋๊ฐ ๊ฐ๊ฒฐํจ
2. Cookie ๊ฐ์ฒด ์์ฑ ํ Cookie ๊ฐ์ฒด์ Token์ ๋ด์ ํ, ๊ทธ Cookie๋ฅผ Response ๊ฐ์ฒด์ ๋ด์์ ์๋ต
- JWT ํ ํฐ ๋ง๋ฃ ๊ธฐํ ๋ฟ๋ง ์๋๋ผ ์ฟ ํค์ ๋ํ ๋ง๋ฃ ๊ธฐํ์ ์ค ์ ์์
- ๋ค๋ฅธ ์ต์ ๋ ์ถ๊ฐํ ์ ์์
- ๋ํ, ์๋ฒ์์ Cookie๋ฅผ ๋ง๋ค๊ณ Header์ Set-Cookie๋ผ๋ ์ด๋ฆ์ผ๋ก ๋์ด๊ฐ๋ฉด ์๋์ผ๋ก Cookie๊ฐ ์ ์ฅ๋จ
// ํ ํฐ ์์ฑ
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username) // ์ฌ์ฉ์ ์๋ณ์๊ฐ(ID)
.claim(AUTHORIZATION_KEY, role) // ์ฌ์ฉ์ ๊ถํ(key-value)
.setExpiration(new Date(date.getTime() + TOKEN_TIME)) // ๋ง๋ฃ ์๊ฐ
.setIssuedAt(date) // ๋ฐ๊ธ์ผ
.signWith(key, signatureAlgorithm) // ์ํธํ ์๊ณ ๋ฆฌ์ฆ(secret Key, ์ ํํ ์๊ณ ๋ฆฌ์ฆ)
.compact();
}
์์ฑ๋ JWT๋ฅผ Cookie์ ์ ์ฅ
// -------- ์์ฑ๋ JWT๋ฅผ Cookie์ ์ ์ฅ --------
public void addJwtToCookie(String token, HttpServletResponse res) {
try {
token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value ์๋ ๊ณต๋ฐฑ์ด ๋ถ๊ฐ๋ฅํด์ encoding ์งํ
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
cookie.setPath("/");
// Response ๊ฐ์ฒด์ Cookie ์ถ๊ฐ
res.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
logger.error(e.getMessage());
}
}
Cookie์ ๋ค์ด์๋ JWT ํ ํฐ Substring
- ์์ ๋ถ์ธ Bearer๋ฅผ ๋ผ๊ธฐ ์ํด Substring ์ํํจ
// JWT ํ ํฐ substring
public String substringToken(String tokenValue) {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
return tokenValue.substring(7);
}
logger.error("Not Found Token");
throw new NullPointerException("Not Found Token");
}
JWT ๊ฒ์ฆ
// ํ ํฐ ๊ฒ์ฆ
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException | SignatureException e) {
logger.error("Invalid JWT signature, ์ ํจํ์ง ์๋ JWT ์๋ช
์
๋๋ค.");
} catch (ExpiredJwtException e) {
logger.error("Expired JWT token, ๋ง๋ฃ๋ JWT token ์
๋๋ค.");
} catch (UnsupportedJwtException e) {
logger.error("Unsupported JWT token, ์ง์๋์ง ์๋ JWT ํ ํฐ ์
๋๋ค.");
} catch (IllegalArgumentException e) {
logger.error("JWT claims is empty, ์๋ชป๋ JWT ํ ํฐ ์
๋๋ค.");
}
return false;
}
JWT์์ ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
// ํ ํฐ์์ ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
JWT ๊ฒ์ฆํ๊ธฐ
package com.sparta.springauth.auth;
import com.sparta.springauth.entity.UserRoleEnum;
import com.sparta.springauth.jwt.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
@RestController
@RequestMapping("/api")
public class AuthController {
...
private final JwtUtil jwtUtil;
public AuthController(JwtUtil jwtUtil){
this.jwtUtil = jwtUtil;
}
...
@GetMapping("/create-jwt")
public String createJwt(HttpServletResponse res) {
// Jwt ์์ฑ
String token = jwtUtil.createToken("Robbie", UserRoleEnum.USER);
// Jwt ์ฟ ํค ์ ์ฅ
jwtUtil.addJwtToCookie(token, res);
return "createJwt : " + token;
}
@GetMapping("/get-jwt")
public String getJwt(@CookieValue(JwtUtil.AUTHORIZATION_HEADER) String tokenValue) {
// JWT ํ ํฐ substring
String token = jwtUtil.substringToken(tokenValue);
// ํ ํฐ ๊ฒ์ฆ
if(!jwtUtil.validateToken(token)){
throw new IllegalArgumentException("Token Error");
}
// ํ ํฐ์์ ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
Claims info = jwtUtil.getUserInfoFromToken(token);
// ์ฌ์ฉ์ username
String username = info.getSubject();
System.out.println("username = " + username);
// ์ฌ์ฉ์ ๊ถํ
String authority = (String) info.get(JwtUtil.AUTHORIZATION_KEY);
System.out.println("authority = " + authority);
return "getJwt : " + username + ", " + authority;
}
...
}


