
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
package com.takensoft.common.util;
import com.takensoft.cms.mber.vo.MberAuthorVO;
import com.takensoft.cms.mber.vo.MberVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* @author : takensoft
* @since : 2025.01.22
* @modification
* since | author | description
* 2025.01.22 | takensoft | 최초 등록
* 2025.06.18 | takensoft | 토큰 추출 로직 통합 및 중복 제거
*
* JWT 토큰 생성 및 검증, 쿠키 생성 등의 유틸리티 - 토큰 추출 로직 통합
*/
@Component
public class JWTUtil {
private static SecretKey JWT_SECRET_KEY;
// 토큰 추출 우선순위 정의
private static final String[] TOKEN_COOKIE_NAMES = {"Authorization", "refresh"};
private static final String BEARER_PREFIX = "Bearer ";
private static final String AUTHORIZATION_HEADER = "Authorization";
/**
* @param secret - JWT 서명을 위한 키 (application.yml에서 값을 읽어 옴)
*
* 기본 생성자
*/
public JWTUtil(@Value("${jwt.secret}")String secret) {
this.JWT_SECRET_KEY = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
}
/**
* @param request - HTTP 요청 객체
* @return 추출된 JWT 토큰 (Bearer 접두사 제거된 순수 토큰) 또는 null
*
* HTTP 요청에서 JWT 토큰을 추출하는 통합 메서드
* 우선순위: Authorization 헤더 → Authorization 쿠키 → refresh 쿠키
*/
public String extractTokenFromRequest(HttpServletRequest request) {
if (request == null) {
return null;
}
// 1. Authorization 헤더에서 토큰 추출 시도
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
if (isValidTokenString(authHeader)) {
return removeBearerPrefix(authHeader);
}
// 2. 쿠키에서 토큰 추출 시도
String tokenFromCookie = extractTokenFromCookie(request);
if (isValidTokenString(tokenFromCookie)) {
return removeBearerPrefix(tokenFromCookie);
}
return null;
}
/**
* @param authHeader - Authorization 헤더 문자열 또는 토큰 문자열
* @return Bearer 접두사가 제거된 순수 토큰 문자열
*
* Bearer 접두사를 제거하는 기존 메서드 (하위 호환성 유지)
*/
public String extractToken(String authHeader) {
return removeBearerPrefix(authHeader);
}
/**
* @param request - HTTP 요청 객체
* @return 쿠키에서 추출된 토큰 문자열 또는 null
*
* 쿠키에서 토큰을 추출하는 전용 메서드
* 우선순위: Authorization 쿠키 → refresh 쿠키
*/
private String extractTokenFromCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return null;
}
// 쿠키 우선순위에 따라 토큰 추출
for (String cookieName : TOKEN_COOKIE_NAMES) {
for (Cookie cookie : cookies) {
if (cookieName.equals(cookie.getName())) {
String cookieValue = cookie.getValue();
if (isValidTokenString(cookieValue)) {
return cookieValue;
}
}
}
}
return null;
}
/**
* @param tokenString - 검증할 토큰 문자열
* @return 유효한 토큰 문자열인지 여부
*
* 토큰 문자열 유효성 검증
*/
private boolean isValidTokenString(String tokenString) {
return tokenString != null &&
!tokenString.trim().isEmpty() &&
!tokenString.equalsIgnoreCase("null") &&
!tokenString.equalsIgnoreCase("undefined");
}
/**
* @param authHeader - Bearer 접두사가 포함될 수 있는 토큰 문자열
* @return Bearer 접두사가 제거된 순수 토큰 문자열
*
* Bearer 접두사 제거 로직
*/
private String removeBearerPrefix(String authHeader) {
if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) {
return authHeader.substring(BEARER_PREFIX.length());
}
return authHeader;
}
/**
* @param category 토큰의 카테고리 정보 (Authorization, refresh)
* @param mbrId 사용자 ID
* @param lgnId 로그인 ID
* @param mbrNm 사용자 이름
* @param roles 사용자 권한 목록
* @param expiredMs 토큰 만료 시간 (밀리초)
* @return 생성된 JWT 토큰 (String)
*
* JWT 토큰 생성
*/
public String createJwt(String category, String mbrId, String lgnId, String mbrNm, List<String> roles, long expiredMs) {
return Jwts.builder()
.claim("category", category)
.claim("mbrId", mbrId)
.claim("lgnId", lgnId)
.claim("mbrNm", mbrNm)
.claim("roles", roles)
.issuedAt(new Date(System.currentTimeMillis())) // 토큰 발행 시간
.expiration(new Date(System.currentTimeMillis() + expiredMs)) // 토큰 소멸 시간
.signWith(JWT_SECRET_KEY)
.compact();
}
/**
* @param key 쿠키 키 값
* @param value 쿠키 값
* @param time 쿠키의 생명주기 (초 단위)
* @return 생성된 Cookie 객체
*
* 쿠키 생성
*/
public Cookie createCookie(String key, String value, int time) {
// 쿠키 생성
Cookie cookie = new Cookie(key, value);
cookie.setMaxAge(time); // 생명주기
//cookie.setSecure(true); // https 통신을 할 경우 true로 사용
cookie.setPath("/"); // 쿠키 적용 범위
cookie.setHttpOnly(true); // front에서 script로 접근 방지
return cookie;
}
/**
* @param tkn JWT 토큰 문자열
* @param knd 조회할 데이터의 종류 (예: ctgry, userId, lgnId 등)
* @return 조회된 클레임 데이터 (종류에 따라 String, Date, List 등으로 반환)
* @throws IllegalArgumentException 유효하지 않은 knd 값일 경우 예외 발생
*
* 클레임 조회
*/
public Object getClaim(String tkn, String knd) {
Claims claims;
try {
// 토큰 값 검증
if (tkn == null || tkn.trim().isEmpty()) {
throw new IllegalArgumentException("Token is null or empty");
} else {
tkn = extractToken(tkn);
}
claims = Jwts.parser()
.verifyWith(JWT_SECRET_KEY)
.build()
.parseSignedClaims(tkn)
.getPayload();
} catch (ExpiredJwtException e) {
// 만료된 토큰이라도 claims 꺼내기 가능
claims = e.getClaims();
} catch (JwtException | IllegalArgumentException e) {
// 토큰 자체가 잘못된 경우
throw new IllegalArgumentException("Invalid token");
}
switch (knd) {
case "category":
return claims.get("category", String.class);
case "mbrId":
return claims.get("mbrId", String.class);
case "lgnId":
return claims.get("lgnId", String.class);
case "mbrNm":
return claims.get("mbrNm", String.class);
case "roles":
List<Map<String, Object>> roles = claims.get("roles", List.class);
List<MberAuthorVO> authorList = new ArrayList<>();
if (roles != null && !roles.isEmpty()) {
for (Map<String, Object> role : roles) {
MberAuthorVO userAuthor = new MberAuthorVO(role.get("authority").toString());
authorList.add(userAuthor);
}
}
return authorList;
case "isExpired":
return claims.getExpiration().before(new Date());
case "expired":
return claims.getExpiration();
default:
throw new IllegalArgumentException("Invalid knd : " + knd);
}
}
}