
JWT (JSON Web Token)는 웹 표준인 RFC 7519에 따라 정의된, 인터넷 상에서 두 개체 사이에서 JSON 객체를 사용하여 가볍고 자가수용적인 방식으로 정보를 안전하게 전송하기 위한 컴팩트하고 독립적인 방법입니다.
자가수용적(self-contained)이란 토큰 자체가 필요한 모든 정보를 포함하고, 토큰을 발급받은 후에는 별도의 정보 없이도 그 토큰만으로 필요한 정보를 검증하고 사용할 수 있다는 의미입니다.
JWT는 주로 인증 및 정보 교환에 사용되며, 세 가지 주요 부분으로 구성됩니다:
헤더(Header): 토큰의 유형(typically JWT)과 사용된 해시 알고리즘(예: HMAC SHA256 또는 RSA)을 설명합니다.
페이로드(Payload): 토큰에 포함될 클레임(claims)을 담고 있습니다. 클레임은 토큰에 대한 정보를 담은 명세로, 여러 종류가 있습니다. 예를 들어, 사용자에 대한 정보, 토큰의 발급자, 유효 기간 등이 포함될 수 있습니다.
서명(Signature): 헤더와 페이로드를 안전하게 결합하기 위해 사용됩니다. 서명은 서버에서 검증할 때 토큰의 무결성을 확인하는 데 사용됩니다. 헤더의 해시 알고리즘에 따라 헤더와 페이로드를 결합한 후 비밀키나 공개키/개인키 쌍을 사용하여 생성됩니다.
JWT의 사용 예시로는 사용자가 로그인을 한 후 서버가 그 사용자에 대한 JWT를 발금하고, 사용자는 이후의 요청에 이 토큰을 포함시켜 서버에 자신을 인증하는 과정이 있습니다. 이 방식은 서버가 각 사용자에 대한 세션을 유지할 필요 없이, 토큰만으로 사용자의 인증 정보를 확인할 수 있게 해주므로 상태가 없는(stateless) 인증 메커니즘을 가능하게 합니다.
JWT는 그 구조로 인해 여러 가지 이점을 제공합니다:
자가 수용적: 필요한 모든 정보를 자체적으로 포함하고 있어 별도의 저장소 접근 없이도 검증이 가능합니다.
경량: JSON 형식을 사용하여 작은 크기로 데이터를 전송할 수 있습니다.
이동성: 웹, URL, 쿠키 등 다양한 매체를 통해 손쉽게 전송될 수 있습니다.
하지만, JWT 사용에 있어서도 주의해야 할 점이 있습니다. 예를 들어, 민감 정보는 페이로드에 암호화되지 않은 채로 포함되므로 JWT가 노출될 경우 정보가 유출될 수 있습니다. 따라서 보안이 중요한 정보는 페이로드에 포함시키기 전에 반드시 암호화하는 것이 좋습니다.
1. 디펜던시 추가
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version> <!-- for JSON parsing -->
</dependency>
</dependencies>
2.JWT 유틸리티 클래스 생성
import io.jsonwebtoken.Claims; // JWT 클레임을 다루기 위한 클래스를 임포트합니다. 클레임은 페이로드에 포함된 정보 조각입니다.
import io.jsonwebtoken.Jwts; // JWT를 생성하고 파싱하는 메소드를 제공하는 클래스를 임포트합니다.
import io.jsonwebtoken.SignatureAlgorithm; // JWT의 서명에 사용할 알고리즘을 지정하는 열거형을 임포트합니다.
import org.springframework.stereotype.Component; // 이 클래스를 Spring의 컴포넌트로 등록하기 위해 필요한 어노테이션을 임포트합니다.
import java.util.Date; // JWT의 유효 기간을 설정하기 위해 사용하는 Date 클래스를 임포트합니다.
@Component // 이 클래스를 Spring의 빈으로 자동 등록하기 위한 어노테이션입니다.
public class JwtTokenUtil { // JWT 유틸리티 클래스의 선언부입니다.
private String secret = "your_secret_key"; // 서명에 사용될 비밀키를 정의합니다.
private long validityInMilliseconds = 3600000; // 토큰의 유효 시간을 밀리초 단위로 설정합니다. 여기서는 1시간(3600000ms)입니다.
public String createToken(String username) { // 사용자 이름을 받아 JWT를 생성하는 메소드입니다.
Claims claims = Jwts.claims().setSubject(username); // 페이로드에 포함될 클레임을 설정합니다. 여기서는 사용자 이름을 주제(subject)로 설정합니다.
Date now = new Date(); // 현재 시간을 생성합니다.
Date validity = new Date(now.getTime() + validityInMilliseconds); // 토큰의 만료 시간을 설정합니다.
return Jwts.builder() // JWT를 생성하기 위한 빌더를 초기화합니다.
.setClaims(claims) // 위에서 정의한 클레임을 JWT에 설정합니다.
.setIssuedAt(now) // 토큰의 발행 시간을 현재 시간으로 설정합니다.
.setExpiration(validity) // 토큰의 만료 시간을 설정합니다.
.signWith(SignatureAlgorithm.HS256, secret) // 사용할 서명 알고리즘과 비밀키를 지정하여 JWT를 서명합니다.
.compact(); // JWT를 문자열로 압축 및 직렬화합니다.
}
public String getUsername(String token) { // JWT로부터 사용자 이름을 추출하는 메소드입니다.
return Jwts.parser() // JWT를 파싱할 파서를 초기화합니다.
.setSigningKey(secret) // 파싱 과정에서 검증에 사용될 서명 키를 설정합니다.
.parseClaimsJws(token) // 파라미터로 받은 JWT를 파싱하여 클레임을 얻습니다.
.getBody() // JWT의 바디(클레임)를 얻습니다.
.getSubject(); // 클레임에서 주제(subject)를 추출합니다. 여기서는 사용자 이름입니다.
}
// Additional methods for token validation... // 토큰 검증에 사용할 추가 메소드를 정의할 수 있는 공간입니다.
}
3. Spring Security 설정
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.apply(new JwtTokenFilterConfigurer(jwtTokenProvider));
}
// Other configurations and beans...
}
4. 인증 엔드포인트 및 사용자 인증
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.apply(new JwtTokenFilterConfigurer(jwtTokenProvider));
}
// Other configurations and beans...
}