JWT 생성의 과정
1. 비밀 키 초기화 (Secret Key Initialization)
- 설정 (Application properties):
jwt.secret.key=7Iqk7YyM66W07YOA7L2U65Sp7YG065+9U3ByaW5n6rCV7J2Y7...
- @Value("${jwt.secret.key}"): 이 애너테이션은 애플리케이션 속성 파일(application.properties 또는 application.yml)에서 비밀 키를 주입합니다. 비밀 키는 JWT를 서명하고 검증하는 데 사용됩니다.
- init() 메서드:
- @PostConstruct 애너테이션을 사용하여 객체가 생성된 후 실행됩니다.
- Base64로 인코딩된 비밀 키를 바이트 배열로 디코딩한 후 Keys.hmacShaKeyFor를 사용하여 암호화 키(Key)를 생성합니다. 이 키는 나중에 JWT 서명에 사용됩니다.
@Component
public class JwtUtil {
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// 사용자 권한 값의 KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token 식별자
public static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 로그 설정
public static final Logger logger = LoggerFactory.getLogger("JWT 관련 로그");
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
2. JWT 생성
createToken(String username, UserRoleEnum role):
- 주체 (setSubject): JWT에 "주체" (일반적으로 사용자 ID 또는 사용자 이름)를 설정합니다.
- 클레임 (claim): 사용자 권한과 같은 추가 정보를 토큰 페이로드에 저장합니다 (여기서는 auth 클레임으로 사용자 권한을 저장).
- 만료 시간 (setExpiration): 토큰의 만료 시간을 정의하며, 생성된 후 1시간으로 설정됩니다 (TOKEN_TIME = 60 * 60 * 1000L).
- 발급일 (setIssuedAt): 토큰이 생성된 시간을 기록합니다.
- 서명 (signWith): HMAC-SHA256 알고리즘과 이전에 초기화된 암호화 키를 사용하여 JWT에 서명합니다. 이는 토큰의 무결성을 보장합니다.
- 결과는 Bearer로 시작하는 전체 JWT 문자열입니다 (규약의 일환).
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username) // 사용자 식별자값(ID)
.claim(AUTHORIZATION_KEY, role) // 사용자 권한
.setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact();
}
3. JWT를 Cookie에 추가
addJwtToCookie(String token, HttpServletResponse res):
- URLEncoder.encode: JWT를 쿠키에 추가하기 전에 URL 인코딩을 수행합니다. 쿠키는 공백을 포함할 수 없기 때문입니다.
- 쿠키(CookieO 생성: Authorization이라는 이름(AUTHORIZATION_HEADER로 설정됨)의 새로운 쿠키를 만들고 JWT를 값으로 설정합니다.
- 응답에 추가: 쿠키는 HttpServletResponse 객체에 추가되고 클라이언트로 전송됩니다.
public void addJwtToCookie(String token, HttpServletResponse res) {
try {
token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
cookie.setPath("/");
// Response 객체에 Cookie 추가
res.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
logger.error(e.getMessage());
}
}
4. Token 추출
substringToken(String tokenValue):
- 이 메서드는 Bearer 로 시작하는 문자열에서 JWT를 추출합니다. 문자열이 비어 있지 않고 올바른 접두사로 시작하는지 확인한 후 접두사(Bearer)를 제거하고 토큰을 반환합니다 (tokenValue.substring(7)).
public String substringToken(String tokenValue) {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
return tokenValue.substring(7);
}
logger.error("Not Found Token");
throw new NullPointerException("Not Found Token");
}
5. Token 검증
validateToken(String token):
- 이 메서드는 토큰을 파싱하여 유효한지 확인합니다.
- Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token):
- 이 코드는 토큰을 디코딩하고 서명을 검증하여 토큰의 유효성을 검사합니다.
- 서명이 유효하지 않거나, 만료되었거나, 지원되지 않는 형식일 경우 예외가 발생하며, 해당 예외는 각각 처리되고 오류 메시지가 기록됩니다.
- 토큰이 유효하면 true를 반환하고, 그렇지 않으면 false를 반환합니다.
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException | SignatureException e) {
logger.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
} catch (ExpiredJwtException e) {
logger.error("Expired JWT token, 만료된 JWT token 입니다.");
} catch (UnsupportedJwtException e) {
logger.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
} catch (IllegalArgumentException e) {
logger.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
}
return false;
}
6. Token에서 사용자 정보를 가져오기
getUserInfoFromToken(String token):
- Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody():
- 이 코드는 토큰을 파싱하고, 사용자 클레임(토큰 생성 시 설정된 사용자 이름과 역할 등)을 포함하는 본문을 반환합니다.
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
7. HTTPServletRequest에서 Cookie Value: JWT 가져오기
public String getTokenFromRequest(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if(cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(AUTHORIZATION_HEADER)) {
try {
return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode 되어 넘어간 Value 다시 Decode
} catch (UnsupportedEncodingException e) {
return null;
}
}
}
}
return null;
}
'Spring Boot' 카테고리의 다른 글
REST API - HTTP Method PUT vs PATCH (0) | 2024.11.01 |
---|---|
Troubleshooting - "GenerationTarget encountered exception accepting command : Error executing DDL " (3) | 2024.10.17 |
Spring Boot - JSON Web Token JWT란 (1) | 2024.10.11 |
Spring Boot - JPA (Java Persistence API) 기초 (0) | 2024.10.08 |
Spring Boot - Inversion of Control (IoC), Dependency Injection (DI) and Bean (0) | 2024.10.07 |