지난번 🔎JWT 기본구조 알아보기 에서 JWT의 구성요소를 살펴보았다.
JWT 토큰 생성 코드를 공부 중 의문이 드는 부분이 있어 기록을 남기려 한다.
JWT의 payload(claims)에 들어가는 정보는 크게 3가지로 분류된다.
- 등록된 클레임(Registered Claims - 표준으로 정해진 클레임)
- 공개 클레임(Public Claims- 구현자 마음대로 만드는 클레임)
- 비공개 클레임(Private Claims - 특정 서비스끼리 약속한 클레임)
아래 토큰 생성 코드에서 Claims 객체 안에는 Payload가 들어간다.
payload정보는 위에서 언급했듯, 등록된 클레임, 공개 클레임, 비공개 클레임이 있는데, jwt 토큰 생성시 claims 안에 직접적으로 subject와 roles를 담고, iat, exp는 개별로 builder를 통해 토큰에 담는 이유가 궁금했다.
JJWT의 권장사항은 다음과 같다.
- claims는 payload에 들어가는 데이터만 담아라
- 발급 시간·만료 시간·서명 등은 builder에 맡겨라
아래의 jjwt 라이브러리 개발팀 공식 리포지토리에서 권장사항을 확인할 수 있다.
GitHub - jwtk/jjwt: Java JWT: JSON Web Token for Java and Android
Java JWT: JSON Web Token for Java and Android. Contribute to jwtk/jjwt development by creating an account on GitHub.
github.com
위의 권장사항을 살펴보면 구현 시 Claims에 담는 payload의 개념이 JWT 기본 구성요소의 "내용(Claim)"부분과 성격이 약간 다른 것 같다.
여기서 payload는 "실질적 데이터", builder로 담는 부분은 metadata의 개념으로 보아야 한다.
즉, iat/exp는 payload+security 메타정보 성격이라 builder에 따로 담는다.
이렇게 분리되는 이유는 안정성과 책임 분리의 문제 때문이다.
iat, exp는 단순 데이터가 아니라 토큰의 보안/유효성 판단 기준이다.
- iat → 언제 발급됐는지 (재발급 로직에 사용)
- exp → 언제 만료되는지 (보안적으로 매우 중요)
- 서명(signWith)과 함께 토큰 유효성을 정의하는 핵심 규칙들
그래서 JJWT 라이브러리는 이런 “보안성 메타 정보”들을 payload와 분리하여 builder로 묶어 관리하게 만들었다.
또한 이런 데이터들을 Claims에 넣으면 문제가 생길 수 있다. iat와 exp처럼 날짜 형식의 데이터는 타입이 다양하기 때문에 파싱과정에서 에러가 발생할 수 있다.
예)
exp가 Date → long 변환 안 맞으면 파싱 에러
iat가 밀리초 단위면 JWT가 오류로 판단
시간이 LocalDateTime 등으로 들어가면 Invalid Claim 발생
이러한 이유들로 메타데이터들을 claim에 넣지 않고 builder로 토큰에 담아주는 것 같다.
@Component
@RequiredArgsConstructor
public class JwtTokenProvider { //UserDetails 정보를 가져와 JWT 토큰 생성
private final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JwtTokenProvider.class);
private final UserDetailsService userDetailsService;
@Value("${springboot.jwt.secret}")
private String secretKey = "secretKey"; //yml파일에서 secretkey 가져오기
private Key key;
private final long tokenValidMillisecond = 1000L * 60 * 60; //토큰 만료 시간 (1시간)
/**
* secretKey를 Base64형식으로 인코딩
*/
@PostConstruct //해당 객체가 bean 객체로 주입된 이후 수행되는 메서드
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));
}
public String createToken(String userUid, List<String> roles) {
LOGGER.info("[createToken] 토큰 생성 시작");
Claims claims = Jwts.claims().setSubject(userUid); //sub속성 추가
claims.put("roles", roles); //roles 추가
Date now = new Date();
//JJWT 0.11.x 이후 key를 직접 생성해서 넣어줘야 함
byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
this.key = Keys.hmacShaKeyFor(keyBytes);
String token = Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + tokenValidMillisecond))
//.signWith(SignatureAlgorithm.HS256, secretKey) //deprecated
.signWith(key, SignatureAlgorithm.HS256) //JJWT 0.11.x 이후 방식 - 주어진 키와 알고리즘(SHA256)으로 서명 생성
.compact();
LOGGER.info("[createToken] 토큰 생성 완료");
return token;
}
'SPRING > Security' 카테고리의 다른 글
| Spring Security FilterChain (0) | 2025.11.19 |
|---|---|
| JWT 기본구조 알아보기 (0) | 2025.10.29 |
| 스프링 시큐리티 흐름 분석: DelegatingFilterProxy와 인증 체계 이해하기 (0) | 2025.10.28 |
| Spring Security 세션 Redis 저장 시 SerializationException 발생 원인과 해결 (0) | 2025.10.27 |
| 250919 (금) 스프링시큐리티 : JWT 로그인 / 정규표현식 (0) | 2025.09.19 |