세션 기반 로그인과 토큰 기반 로그인(JWT) 차이
세션 기반
- 사용자가 로그인 요청
- 서버가 아이디/비밀번호 확인
- 서버가 세션 생성
- 서버가 세션 ID를 브라우저에 쿠키로 전달
- 이후 요청마다 브라우저는 쿠키의 세션 ID 전송
- 서버는 세션 저장소를 조회해서 로그인 여부 확인
토큰 기반(JWT)
- 사용자가 로그인 요청
- 서버가 아이디/비밀번호 확인
- 서버가 JWT 발급
- 클라이언트가 JWT 저장
- 이후 요청마다 Authorization: Bearer <토큰> 형태로 전송
- 서버는 JWT 서명, 만료시간 등을 검사해서 인증
JWT( JSON Web Token )란?
JWT
: 사용자 정보와 만료시간 같은 내용을 담고, 위조되지 않았는지 검증할 수 있게 만든 문자열 토큰
로그인 후에는 서버가 매 요청마다 요청 보낸 사람이 누구인지를 확인해야 한다.
예전엔 세션 방식으로 많이 했고, JWT 방식에서는 로그인 성공 후 서버가 토큰을 하나 발급해준다.
그 다음부터 사용자는 요청할 때마다 이 토큰을 함께 보내고, 서버는 그 토큰을 보고 사용자를 확인한다.
- 로그인 성공 → JWT 발급
- 이후 요청 → JWT 같이 보냄
- 서버 → JWT 검사 후 인증 처리
JWT의 3부분
1) Header
토큰의 정보가 들어 있다 (어떤 알고리즘으로 서명했는지, 이게 JWT인지)
2) Payload
실제 데이터가 들어 있다
- 사용자 id
- username
- 권한(role)
- 만료시간(exp)
3) Signature
이 토큰이 위조되지 않았는지 확인하는 서명
- header + payload를 바탕으로 서버의 secret key로 서명 생성
- 서버는 나중에 이 서명을 다시 계산해서 비교함
JWT는 암호화가 아니라 “서명”이 핵심이다
JWT는 기본적으로 내용을 숨기는 용도가 아니다.
보통 header와 payload는 디코딩하면 읽을 수 있기 때문
대신 중요한 건
- 내용을 읽을 수는 있어도
- 마음대로 바꾸지는 못한다
바꾸면 signature 검증에서 걸림
그래서 JWT 안에는 민감한 개인정보를 넣으면 안된다
로그인에서 JWT가 쓰이는 흐름
- 사용자가 아이디/비밀번호로 로그인
- 서버가 사용자 확인
- 서버가 JWT 발급
- 클라이언트가 JWT 저장
- 이후 API 요청 때 JWT를 함께 보냄
보통 이렇게 보냄
Authorization: Bearer eyJhbGciOiJIUzI1Ni...
서버가 JWT 검증해서 성공하면 “이 유저는 인증된 사용자”라고 판단
JWT의 장점
- 서버가 세션 저장을 안 해도 됨
- 세션 방식은 서버가 로그인 상태를 저장해야 했는데,
JWT는 토큰 자체에 정보가 있으니까 상대적으로 관리가 단순
- 세션 방식은 서버가 로그인 상태를 저장해야 했는데,
- 프론트/백 분리에 잘 맞음
- React, React Native, 모바일 앱, REST API 구조에서 많이 씀.
- 확장성에 유리
- 서버 여러 대를 운영할 때 세션 공유 부담이 줄어듦.
JWT의 단점
- 탈취되면 위험
- 발급 후 강제 무효화가 까다로움
- 세션은 서버에서 지우면 끝인데, JWT는 이미 클라이언트가 가지고 있으니까 바로 끊기 어렵다.
- 토큰 안에 민감한 정보 넣으면 안 됨
JWT 구현해보기
1. 목표
- Spring Boot 기반 프로젝트에 JWT 기반 로그인 기능을 구현하고,
Postman을 통해 다음 흐름을 검증하는 것이 목표였다.- 회원가입
- 로그인 (JWT 발급)
- JWT를 이용한 인증 요청
2. 구현 전 상태
- 기존 프로젝트는 post/comment 중심
- 로그인 기능 없음
- Spring Security, JWT 라이브러리 미적용
3. 구현 내용
3-1. 의존성 추가


- security
- jjwt
- validation
- jpa, mysql
3-2. 회원가입 기능
- Member 엔티티
- MemberRepository
- SignupRequest, SignupResponse
- POST /api/members/signup
요청
POST /api/members/signup
요청 Body
{
"username": "minju",
"password": "1234"
}
결과
- DB에 회원 정보 저장
- 응답으로 회원 정보 반환
3-3. 로그인 기능
- LoginRequest, LoginResponse
- POST /api/members/login
- username/password 검증 후 JWT 발급
요청
POST /api/members/login
요청 Body
{
"username": "minju",
"password": "1234"
}
처리 과정
- username으로 사용자 조회
- password 비교
- 일치하면 JWT 생성
응답
{
"accessToken": "eyJhbGciOiJIUzI1NiJ9..."
}
이 토큰이 앞으로 인증에 사용된다.
3-4. JWT 유틸
- JwtTokenProvider
- 토큰 생성
- 토큰 검증
- username 추출
3-5. JWT 필터
모든 요청은 JwtAuthenticationFilter를 통과한다.
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
String token = bearerToken.substring(7);
if (jwtTokenProvider.validateToken(token)) {
String username = jwtTokenProvider.getUsername(token);
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(
username, null, AuthorityUtils.NO_AUTHORITIES
)
);
}
}
- JwtAuthenticationFilter
- Authorization 헤더 확인
- Bearer 토큰 추출
- SecurityContext에 인증 정보 저장
3-6. Security 설정
- /signup, /login은 허용
- 나머지는 인증 필요
- 세션 대신 STATELESS
3-7. 인증 확인 API
- GET /api/members/me
- 토큰 없으면 401
- 토큰 있으면 인증 성공
4. Postman 테스트 결과


- 회원가입 성공
- 로그인 성공, accessToken 발급
- /me 요청 성공
5. 트러블슈팅..
트러블슈팅 1. 401 Unauthorized
- 원인: 회원가입 시도하자 Security 기본 설정 때문에 회원가입 API가 막힘
- 해결: SecurityConfig에서 permitAll 설정
.requestMatchers("/api/members/signup", "/api/members/login").permitAll()
트러블슈팅 2. Could not resolve placeholder 'jwt.secret'
- 원인: application.yaml에서 jwt 설정 위치 문제, spring 아래에 두었었음
- 해결: 공통 영역으로 이동
트러블슈팅 3. cannot find symbol JwtTokenProvider
- 원인: 파일/인식 문제 또는 빌드 동기화 문제
- 해결:캐시 초기화로도 해결이 안 됐는데 rebuild를 하니 해결!
6. 이번 구현으로 이해한 점
- JWT는 로그인 후 발급받는 인증 토큰
- 서버는 세션을 저장하지 않고, 매 요청마다 토큰을 검증
- 필터가 인증의 핵심 연결 지점
- Spring Security는 기본 설정만으로도 요청을 막기 때문에 허용 경로를 명시해야 함
7. 다음 개선 방향
- 비밀번호 암호화 (PasswordEncoder)
- 권한 처리 (작성자만 수정/삭제)
- 예외 처리 구조 개선
- 게시글(Post)/댓글(Comment) 작성자와 사용자 연결