참고할 만한 추가적인 게시글들 :
https://taegyunwoo.github.io/tech/Tech_JWT#header-%ED%97%A4%EB%8D%94
https://velog.io/@junho5336/SpringBoot-JWT-%EC%A0%81%EC%9A%A9#jwt-%EC%83%9D%EC%84%B1
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
// JWT (JSON Web Token)를 생성, 파싱, 검증하는 데 사용되는 기본 API를 제공
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
//jjwt-api의 구현체로, JWT의 생성, 파싱, 검증 등의 동작을 구현
//이 라이브러리를 사용하면 jjwt-api에서 정의된 기능을 실제로 사용할 수 있다.
//따라서 jjwt-impl은 JWT 처리에 필수적
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2'
//JWT -> JSON 직렬화, 혹은 JSON -> JWT로 역직렬화하기 위한 기능을 제공
//Jackson JSON 라이브러리를 기반으로 동작하며, JWT를 JSON으로 변환하는 데 유용
이 부분에서 추가적으로 정리한 부분을 살펴보자.
서명 : JWT는 서버의 시크릿 키를 사용하여 서명되었다.
검증 과정에서는 수신한 JWT의 서명을 해당 시크릿 키를 사용하여 검증한다. 이를 통해 JWT가 변조되지 않았는지 확인할 수 있다.
만료 시간 확인 : JWT에는 만료 시간이 포함되어 있다.
검증 과정에서는 현재 시간과 JWT의 만료 시간을 비교하여 JWT가 만료되었는지 확인한다. 이때 시간이 만료되었다면, 유효하지 않은 토큰인 것이다.
추가적인 클레임 검증 : JWT의 클레임(Claims)에는 사용자 정보나 추가 데이터가 포함될 수 있다.
클레임 검증 예시 코드
public static void validateClaims(Claims claims) {
String role = claims.get("role", String.class);
if (role == null || !role.equals("admin")) {
throw new JwtException("Unauthorized access");
}
}
.setHeaderPrameter()
로 메시지 헤더에 값을 넣을 수 있고, .claim()
을 통해 메시지 바디에 값을 추가할 수 있다.
알고리즘과 타입과 같은 기본 정보는 토큰의 헤더에 넣고, 사용자가 판별한 고유 정보나 추가 정보는 토큰의 페이로드에 비공개 클레임으로 넣는 것이 권장된다.
//JWT 생성과 검증에 관한 코드
package com.sas.refactoring.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class JwtUtil {
private static final String SECRET_KEY = "insertYourSecretKeyinsertYourSecretKeyinsertYourSecretKey"; //시크릿 키 설정
//Access Token 생성
public String createAccessToken(Long memberId) {
LocalDateTime currentTime = LocalDateTime.now();
LocalDateTime expirationTime = currentTime.plus(30, ChronoUnit.MINUTES);
ZoneId zoneId = ZoneId.systemDefault();
ZonedDateTime zonedDateTime = expirationTime.atZone(zoneId);
Date expirationDate = Date.from(zonedDateTime.toInstant());
return createToken(memberId, "access-token", expirationDate);
}
//Refresh Token 생성
public String createRefreshToken(Long memberId) {
LocalDateTime currentTime = LocalDateTime.now();
LocalDateTime expirationTime = currentTime.plus(1, ChronoUnit.DAYS);
ZoneId zoneId = ZoneId.systemDefault();
ZonedDateTime zonedDateTime = expirationTime.atZone(zoneId);
Date expirationDate = Date.from(zonedDateTime.toInstant());
return createToken(memberId, "refresh-token", expirationDate);
}
//JWT 토큰 생성 후 리턴
public static String createToken(Long memberId, String subject, Date date) {
//토큰 중복 or 만료 여부
// Member member = memberService.getMemberById(memberId).orElseThrow(
// () -> new UnAuthenticationException(CustomExceptionStatus.AUTHENTICATION_MEMBER_IS_NULL));
//1. 헤더 객체 생성. 헤더에는 필수적인 정보만 들어갈 것.
Map<String, Object> headerMap = new HashMap<String, Object>();
headerMap.put("typ", "JWT");
headerMap.put("alg", "HS256");
//2. 클레임 객체 생성 : JWT의 페이로드에 저장될 클레임(토큰에 저장할 사용자 정보 혹은 기타 데이터.. )을 설정하는데 사용
// Claims claims = Jwts.claims().setSubject(subject); 이런 방식도 가능
Map<String, Object> claims = new HashMap<String, Object>();
claims.put("name", "이름");
claims.put("id", "id들어감");
//3. Jwts.builder를 사용해 빌더 객체를 리턴.
//리턴된 값은 FE쪽으로 처리됨
return Jwts.builder()
.setHeader(headerMap) //1번의 헤더맵 설정 - header
.setClaims(claims) //2번의 클레임을 설정 - messagebody, claim
.setExpiration(date) //시간 설정
.setSubject(subject)
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) //시크릿 키 활용, 서명 생성
.compact(); //JWT를 생성하고 문자열로 반환
}
public static boolean validateToken(String token) {
try{
Jwts.parser().setSigningKey(SECRET_KEY)
.parseClaimsJws(token); // parseClaimsJws 메서드를 사용해 토큰을 파싱합니다.
log.info("올바른 토큰입니다.");
return true;
}
catch (Exception e) {
log.info("올바르지 않은 토큰입니다");
return false;
}
}
}