🔐 JWT (JSON Web Token) 정리
웹·앱 환경에서 사용되는 토큰 기반 인증 방식에 대한 정리.
인증(Authentication)과 인가(Authorization)를 분리하고,
세션(Session)보다 효율적이며 확장성 높은 인증 구조를 제공함.
🧭 인증 vs 인가
| 구분 | 설명 |
|---|
| 인증 (Authentication) | 사용자가 누구인지 확인하는 과정 |
| 인가 (Authorization) | 인증된 사용자가 접근 권한이 있는지 확인하는 과정 |
🔧 인증 방법
| 구분 | 설명 |
|---|
| 세션 (Session) | 서버 메모리에 사용자 정보를 저장하여 인증 유지 (Spring Security 등) |
| 쿠키 (Cookie) | 브라우저에 토큰/식별정보 저장 (보조 보안 수단으로 JWT와 함께 사용) |
| JWT (JSON Web Token) | 서버 상태를 저장하지 않고 클라이언트에 인증 정보를 담는 토큰 방식 |
🧱 JWT 정의
- JWT (JSON Web Token)
: JSON 형식의 데이터를 안전하게 주고받기 위한 토큰 기반 인증 방식
🎯 JWT 목적
- 웹(Web) / 모바일(App) 환경에서 인증과 권한 부여(Authorization) 관리에 사용
- 세션을 대체하여 서버의 부하 감소
- REST API 환경에서 인증에 적합
⚙️ JWT 장점
- HTTP 지원: Header에 토큰을 포함하여 요청 가능
- 보안성: 비밀키 기반의 서명(Signature)으로 변조 방지 (기본 알고리즘:
HS256)
- 무상태성 (Stateless): 서버가 클라이언트 상태를 직접 관리하지 않아 확장성↑
🧩 JWT 구조
헤더(Header).페이로드(Payload).서명(Signature)
| 구성요소 | 설명 |
|---|
| Header | 알고리즘 정보, 토큰 타입(JWT) |
| Payload | 실제 데이터(클레임, 발급시간 등) |
| Signature | 헤더+페이로드를 비밀키로 서명한 값 |
💡 비밀키(secret key)는 개발자가 직접 지정한 난수 문자열을 사용해야 하며,
외부에 노출되면 절대 안 됨.
🚀 JWT 확장
| 연동 대상 | 설명 |
|---|
| 쿠키 (Cookie) | JWT를 클라이언트 브라우저에 저장 |
| Redis (NoSQL) | 분산 서버 간 토큰 공유 및 블랙리스트 관리에 사용 가능 |
⚙️ JWT 사용법
1️⃣ Gradle 의존성
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
2️⃣ 비밀키 생성
@Service
@RequiredArgsConstructor
public class JwtService {
private final String secretSample = "12345678901234567890123456789012";
private final Key secretKeySample =
Keys.hmacShaKeyFor(secretSample.getBytes(StandardCharsets.UTF_8));
}
StandardCharsets.UTF_8 은 한글·특수문자 포함 시 필수.
난수는 잊지 않도록 코드 내 직접 관리 또는 환경변수로 관리하는 것이 좋음.
3️⃣ 토큰 관련 메서드
(1) 토큰 생성
public String createTokenSample(String data) {
String token = Jwts.builder()
.claim("key", data)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 30))
.signWith(secretKeySample, SignatureAlgorithm.HS256)
.compact();
return token;
}
(2) 토큰 검증 및 유효성 확인
public boolean checkTokenSample(String token) {
try {
Jwts.parser()
.setSigningKey(secretKeySample)
.build()
.parseClaimsJws(token);
return true;
} catch (JwtException e) {
System.out.println("토큰 없음 또는 유효하지 않음");
return false;
}
}
(3) 토큰에서 데이터 추출
public String payloadTokenSample(String token) {
try {
Claims claims = Jwts.parser()
.setSigningKey(secretKeySample)
.build()
.parseClaimsJws(token)
.getBody();
System.out.println("claims = " + claims);
return claims.get("key", String.class);
} catch (Exception e) {
return null;
}
}
(3-2) 특정 필드 추출용 함수
public String getUid(String token) {
return getClaims(token).get("uid", String.class);
}
public String getUrole(String token) {
return getClaims(token).get("urole", String.class);
}
🧠 추가 팁
| 항목 | 설명 |
|---|
| 만료시간 | 짧게 설정 후 Refresh Token으로 재발급 구조 구성 |
| 비밀키 관리 | .env 또는 Spring application.yml에 분리 |
| Redis 연동 | 토큰 블랙리스트 저장, 로그아웃 시 즉시 무효화 |
| JWT + Cookie | 쿠키에 HttpOnly, Secure, SameSite 옵션 설정 필수 |
| CORS 허용 | JWT를 헤더에 담을 경우, Access-Control-Allow-Headers에 Authorization 추가 필요 |
✅ 핵심 요약
| 구분 | 설명 |
|---|
| JWT 구조 | Header.Payload.Signature |
| 서명 알고리즘 | HS256 (대칭키 기반) |
| 장점 | 무상태, 확장성, 보안성 |
| 단점 | 토큰 크기 증가, 만료 전까지 무효화 불가 |
| 권장 조합 | JWT + Redis / JWT + Cookie |