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 java.nio.charset.StandardCharsets; import java.util.*; /** * @author : takensoft * @since : 2025.01.22 * @modification * since | author | description * 2025.01.22 | takensoft | 최초 등록 * * JWT 토큰 생성 및 검증, 쿠키 생성 등의 유틸리티 */ @Component public class JWTUtil { private static SecretKey JWT_SECRET_KEY; /** * @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 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 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; } // 로그인 사용자 아이디 조회 public String getWriter() { String mbrId = null; Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(authentication != null && authentication.isAuthenticated()) { Object principal = authentication.getPrincipal(); if(principal instanceof UserDetails) mbrId = ((MberVO) authentication.getPrincipal()).getMbrId(); } return mbrId; } /** * @param tkn JWT 토큰 문자열 * @param knd 조회할 데이터의 종류 (예: ctgry, userId, loginId 등) * @return 조회된 클레임 데이터 (종류에 따라 String, Date, List 등으로 반환) * @throws IllegalArgumentException 유효하지 않은 knd 값일 경우 예외 발생 * * 클레임 조회 */ public Object getClaim(String tkn, String knd) { Claims claims; try { 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> roles = claims.get("roles", List.class); List authorList = new ArrayList<>(); if (roles != null && !roles.isEmpty()) { for (Map 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); } } }