JSON Web TokenJWT는 당사자 간에 정보를 JSON 객체로 안전하게 전송하기 위한 표준이다. 웹에서 인증과 권한 부여를 구현할 때 가장 널리 사용되는 수단 중 하나이다. 토큰 자체가 사용자의 권한 정보나 서비스 상태를 포함하고 있어, 이를 수신하는 서버는 별도의 데이터베이스 조회 없이도 토큰 내의 정보만으로 요청을 처리할 수 있다.
header.payload.signatureJWT는 마침표(.)를 구분자로 하여 세 가지 부분으로 나뉜다. 각 부분은 Base64Url 방식으로 인코딩되어 하나의 문자열을 형성한다.
헤더는 토큰에 대한 메타데이터를 담고 있다. 일반적으로 두 가지 정보를 포함한다.
{
"alg": "HS256",
"typ": "JWT"
}
alg: 서명 시 사용된 해싱 알고리즘 (예: HS256, RS256)typ: 토큰의 유형 (JWT)페이로드에는 전달하고자 하는 실제 정보인 Claim 이 포함된다.
{
"name": "Hong",
"email": "Hong@example.com",
}
Claim 은 크게 세 가지 종류로 구분
Claims
iss(발행자), exp(만료 시간), sub(제목), iat(발행 시간) 등Claims
Claims
Base64Url 로 단순 인코딩됨Signature 는 토큰의 무결성을 증명하는 핵심 부분이다. 인코딩된 헤더와 페이로드를 합친 뒤, 서버가 안전하게 보관 중인 Secret Key 를 사용하여 헤더에 명시된 알고리즘으로 해싱한다. 이를 통해 서버는 전달받은 토큰이 위변조되지 않았음을 검증할 수 있다.
로그인 상태를 유지하는 방식은 서버의 상태 저장 여부에 따라 크게 두 가지로 나뉜다.
세션전통적인 세션 방식은 서버가 사용자의 상태를 메모리나 데이터베이스에 유지한다.
JWTJWT는 서버가 상태를 저장하지 않는 Stateless 아키텍처를 지향한다.
| 구분 | 세션/쿠키 방식 | JWT 방식 |
|---|---|---|
| 저장 위치 | 서버(Memory/DB) 및 클라이언트 쿠키 | 클라이언트 (Local Storage/Cookie) |
| 확장성 | 낮음 (세션 동기화 필요) | 높음 (어느 서버에서나 검증 가능) |
| 보안성 | 세션 ID 탈취 시 위험하지만 서버 제어 가능 | 토큰 탈취 시 만료 전까지 무방비 상태 |
탈취 시 제어 불가능 문제를 해결할 수 있을까?이를 위해 Access Token 과 Refresh Token 을 병행하는 전략이 사용된다. 이 방식을 통해 사용자는 빈번한 로그인 없이 서비스를 이용할 수 있으며, 서버는 Refresh Token 을 검증하거나 차단함으로써 사용자의 접속을 제어할 수 있는 수단을 갖게 된다. 최근에는 보안성을 더 높이기 위해 Refresh Token 을 한 번 사용하면 폐기하고 재발급하는 방법도 사용하고 있다.
쿠키와 JWT는 사실 완전히 상이한 개념이다.
그렇다면 클라이언트에서 토큰을 어디에 저장하는게 좋을까? 브라우저의 로컬 스토리지에 담을 수도 있고, 쿠키에 담아 보관할 수도 있다.
// HTTP 응답
HTTP/1.1 200 OK
Content-Type: application/json
{
"token": "eyJhbGci...",
"username": "hong"
}
JWT 토큰을 로컬 스토리지에 보관하기 위해서는, 백엔드가 Response Body에 토큰을 담아보내면 된다. 그러면 프론트에서 토큰을 받아 로컬스토리지에 저장하면 된다.
// 백엔드 (Cookie 방식)
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request,
HttpServletResponse response) {
// 인증 처리
String token = jwtTokenProvider.createToken(username);
// 쿠키로 토큰 전달
Cookie cookie = new Cookie("jwt", token); // 쿠키에 담아서 줌
cookie.setHttpOnly(true); // JS 접근 차단
cookie.setSecure(true); // HTTPS만
cookie.setMaxAge(3600); // 1시간
cookie.setPath("/");
response.addCookie(cookie);
return ResponseEntity.ok(new LoginResponse("로그인 성공"));
}
JWT 토큰을 쿠키에 저장하기 위해서는 백엔드에서 보낼 때 Cookie 객체에 담아서 보내면 된다.
보안을 위한 몇가지 설정을 하고 쿠키 객체에 담아서 보내면 프론트에서 추가작업 없이 자동적으로 브라우저의 쿠키영역에 토큰이 저장된다.
그래서 어디에 저장하는게 좋을까?
| 구분 | Local Storage | HttpOnly Cookie |
|---|---|---|
| XSS 공격 | 취약 (JS 접근 가능) | 안전 (HttpOnly시 JS 접근 불가) |
| CSRF 공격 | 안전 | 취약 (자동 요청) |
| 용량 제한 | 약 5MB | 약 4KB |
이렇게 하면 XSS로 Access Token이 털려도 금방 만료되어 피해가 적고, 핵심인 Refresh Token은 자바스크립트가 접근할 수 없어 안전하게 보호 가능!
Filter Chain
SecurityContext
Authorization
BCryptPasswordEncoder
JwtTokenProvider
JwtAuthenticationFilter
UserDetailsService
AuthService
세션도 있는데 왜 쿠키가 나오게 된거? 세션인증이 필요한
JWT 인증
Session 인증
// 로그인
POST /login → 토큰 생성 → "eyJhbG..." 반환
// 이후 요청
GET /api/posts + Header(토큰) → Filter에서 검증 → Controller