Spring Boot 3 & Security 6 시리즈: JWT Util 클래스 작성 (7편)

2023. 8. 7. 22:38·Spring/Spring Security
반응형
 
 

JWT 유틸리티 클래스 작성하기

 

1. JWT - Util 클래스 작성하기

  • 이 코드는 JWT(JSON Web Token)를 생성하고 검증하는 데 사용되는 TokenUtils 클래스에 대한 것이다. 클래스는 여러 메서드를 포함하고 있으며, JWT의 생성, 유효성 검증 및 관련 정보 추출과 같은 다양한 기능을 수행한다.
@Slf4j
@Component
public class TokenUtils {


    private static final String jwtSecretKey = "thisIsASecretKeyUsedForJwtTokenGenerationAndItIsLongEnoughToMeetTheRequirementOf256Bits";
    // jwtSecretKey를 바이트 배열로 변환하고, 이를 사용하여 HMAC-SHA256 알고리즘에 사용할 키를 생성한다.
    private static final Key key = Keys.hmacShaKeyFor(jwtSecretKey.getBytes(StandardCharsets.UTF_8));
    private static final String JWT_TYPE = "JWT";
    private static final String ALGORITHM = "HS256";
    private static final String LOGIN_ID = "loginId";
    private static final String USERNAME = "username";


    /**
     * 사용자 pk를 기준으로 JWT 토큰을 발급하여 반환해 준다.
     */
    public static String generateJwtToken(UserDto userDto) {

        JwtBuilder builder = Jwts.builder()
                .setHeader(createHeader())                                  // Header 구성
                .setClaims(createClaims(userDto))                           // Payload - Claims구성
                .setSubject(String.valueOf(userDto.loginId()))              // Payload - Subjects구성
                .setIssuer("profile")                                       // Issuer 구성
                .signWith(key, SignatureAlgorithm.HS256)                    // Signature 구성 : 이 키를 사용하여 JWT 토큰에 서명을 추가한다. 이 서명은 토큰의 무결성을 보장하는 데 사용된다.
                .setExpiration(createExpiredDate());                        // Expired Date 구성

        return builder.compact();
    }

    /**
     * 토큰을 기반으로 사용자의 정보를 반환해주는 메서드
     */
    public static boolean isValidToken(String token) {
        try {
            Claims claims = getClaimsFormToken(token);

            log.info("expireTime : " + claims.getExpiration());
            log.info("loginId : " + claims.get(LOGIN_ID));
            log.info("username : " + claims.get(USERNAME));

            return true;
        } catch (ExpiredJwtException expiredJwtException) {
            log.error("Token Expired", expiredJwtException);
            return false;
        } catch (JwtException jwtException) {
            log.error("Token Tampered", jwtException);
            return false;
        } catch (NullPointerException npe) {
            log.error("Token is null", npe);
            return false;
        }
    }

    /**
     * 토큰의 만료기간을 지정하는 함수
     * @return Date
     */
    private static Date createExpiredDate() {
        // 토큰의 만료기간은 8시간으로 지정
        Instant now = Instant.now();
        Instant expiryDate = now.plus(Duration.ofHours(8));
        return Date.from(expiryDate);
    }

    /**
     * JWT의 헤더값을 생성해주는 메서드
     */
    private static Map<String, Object> createHeader() {
        Map<String, Object> header = new HashMap<>();

        header.put("typ", JWT_TYPE);
        header.put("alg", ALGORITHM);
        header.put("regDate", System.currentTimeMillis());
        return header;
    }

    /**
     * 사용자 정보를 기반으로 클래임을 생성해주는 메서드
     * @param userDto 사용자 정보
     * @return Map<String, Object>
     */
    private static Map<String, Object> createClaims(UserDto userDto) {
        // 공개 클래임에 사용자의 이름과 이메일을 설정해서 정보를 조회할 수 있다.
        Map<String, Object> claims = new HashMap<>();

        log.info("loginId : " + userDto.loginId());
        log.info("username : " + userDto.username());

        claims.put(LOGIN_ID, userDto.loginId());
        claims.put(USERNAME, userDto.username());
        return claims;
    }

    /**
     * 토큰 정보를 기반으로 Claims 정보를 반환받는 메서드
     * @return Claims : Claims
     */
    private static Claims getClaimsFormToken(String token) {
        return Jwts.parserBuilder().setSigningKey(key)
                .build().parseClaimsJws(token).getBody();
    }

    /**
     * 토큰을 기반으로 사용자 정보를 반환받는 메서드
     * @return String : 사용자 아이디
     */
    public static String getUserIdFromToken(String token) {
        Claims claims = getClaimsFormToken(token);
        return claims.get(LOGIN_ID).toString();
    }

}

 

1-2. JWT 토큰 생성

  • generateJwtToken 메서드는 UserDto 객체를 매개변수로 받아 JWT 토큰을 생성한다. 토큰은 헤더, 클레임(claim), 서명으로 구성되며, 각 부분은 다음과 같은 목적을 가진다.
  1. 헤더(createHeader 메서드): JWT의 타입과 사용된 알고리즘을 정의한다. 여기서는 "JWT" 타입과 "HS256" 알고리즘이 사용된다.
  2. 클레임(createClaims 메서드): 사용자의 정보를 담는다. 이 경우, 사용자의 loginId와 username이 저장된다.
  3. 서명: 생성된 토큰의 무결성을 보장하기 위해 HMAC-SHA256 알고리즘을 사용한 서명이 추가된다.
  4. 만료시간(createExpiredDate 메서드): 토큰의 만료시간을 설정한다. 이 코드에서는 8시간 후로 설정되어 있다.
public static String generateJwtToken(UserDto userDto) {

		// JWT 토큰을 생성하기 위한 빌더 객체를 초기화한다.
    JwtBuilder builder = Jwts.builder()
            .setHeader(createHeader())                                  // Header 구성
            .setClaims(createClaims(userDto))                           // Payload - Claims구성
            .setSubject(String.valueOf(userDto.loginId()))              // Payload - Subjects구성
            .setIssuer("profile")                                       // Issuer 구성
            .signWith(key, SignatureAlgorithm.HS256)                    // Signature 구성 : 이 키를 사용하여 JWT 토큰에 서명을 추가한다. 이 서명은 토큰의 무결성을 보장하는 데 사용된다.
            .setExpiration(createExpiredDate());                        // Expired Date 구성

    return builder.compact();
}

 

1-3. JWT 토큰 검증

  • isValidToken 메서드는 주어진 JWT 토큰이 유효한지 검증한다. 토큰의 만료시간, 변조 여부, 널 여부를 체크하며, 각각의 경우에 대해 로그를 남기고, 유효하지 않은 경우 false를 반환한다.
public static boolean isValidToken(String token) {
    try {
        Claims claims = getClaimsFormToken(token);

        log.info("expireTime : " + claims.getExpiration());
        log.info("loginId : " + claims.get(LOGIN_ID));
        log.info("username : " + claims.get(USERNAME));

        return true;
    } catch (ExpiredJwtException expiredJwtException) {
        log.error("Token Expired", expiredJwtException);
        return false;
    } catch (JwtException jwtException) {
        log.error("Token Tampered", jwtException);
        return false;
    } catch (NullPointerException npe) {
        log.error("Token is null", npe);
        return false;
    }
}

1-4. createExpiredDate 메서드

  • createExpiredDate 메서드는 JWT의 만료 시간을 설정한다. 이 메서드에서는 현재 시간으로부터 8시간 후를 만료 시간으로 설정하고 있다. 만료 시간은 토큰의 유효 기간을 정의하는 중요한 부분으로, 이 시간이 지나면 토큰이 더 이상 유효하지 않게 된다. 만료 시간을 설정함으로써, 오래된 토큰을 통한 잠재적인 보안 위험을 줄일 수 있다.
private static Date createExpiredDate() {
    // 토큰의 만료기간은 8시간으로 지정
    Instant now = Instant.now();
    Instant expiryDate = now.plus(Duration.ofHours(8));
    return Date.from(expiryDate);
}

1-5. createHeader 메서드

  • createHeader 메서드는 JWT의 헤더를 생성한다. JWT 헤더는 토큰의 타입과 사용된 알고리즘에 대한 정보를 담는 부분이다. 이 메서드에서는 헤더에 'JWT' 타입(typ)과 'HS256' 알고리즘(alg)을 설정하고 있다. 또한, 현재 시간을 밀리초 단위로 표시하는 'regDate' 필드를 포함시켜 토큰이 생성된 시간을 기록한다. 이 정보는 토큰을 검증하는 데 필요한 기본적인 메타데이터를 제공한다.
private static Map<String, Object> createHeader() {
    Map<String, Object> header = new HashMap<>();

    header.put("typ", JWT_TYPE);
    header.put("alg", ALGORITHM);
    header.put("regDate", System.currentTimeMillis());
    return header;
}

1-6. createClaims 메서드

  • createClaims 메서드는 JWT의 클레임(claim)을 구성한다. 클레임은 토큰의 내용을 담는 부분으로, 사용자의 정보나 권한 등의 데이터를 포함할 수 있다. 이 메서드에서는 UserDto 객체를 받아 사용자의 loginId와 username을 클레임에 포함시키고 있다. 이렇게 설정된 클레임은 토큰을 사용하는 사용자의 식별 정보를 제공하며, 서버에서 사용자를 인식하고 권한을 체크하는 데 사용된다.
private static Map<String, Object> createClaims(UserDto userDto) {
    // 공개 클래임에 사용자의 이름과 이메일을 설정해서 정보를 조회할 수 있다.
    Map<String, Object> claims = new HashMap<>();

    log.info("loginId : " + userDto.loginId());
    log.info("username : " + userDto.username());

    claims.put(LOGIN_ID, userDto.loginId());
    claims.put(USERNAME, userDto.username());
    return claims;
}

1-7. 토큰에서 사용자 정보 추출

  • getUserIdFromToken 메서드는 토큰에 저장된 사용자의 loginId를 반환한다. 이 메서드는 getClaimsFormToken 메서드를 사용하여 토큰의 클레임을 추출하고, 이를 기반으로 사용자 ID를 얻는다.
private static Claims getClaimsFormToken(String token) {
    return Jwts.parserBuilder().setSigningKey(key)
            .build().parseClaimsJws(token).getBody();
}
public static String getUserIdFromToken(String token) {
    Claims claims = getClaimsFormToken(token);
    return claims.get(LOGIN_ID).toString();
}

 

 

 

다음 포스트에서는 시큐리티에서 사용할 UserDetailService와 Dto객체를 생성해보자👇🏻👇🏻

 

Spring Boot 3 & Security 6 시리즈: UserDetailsService, DTO 작성하기 (8편)

이번에는 UserDetailsService, DTO 클래스를 작성해 보자 1. CustomUserDetailsService 코드 작성 1-1. 코드 작성하기 이 코드는 Spring Security의 사용자 인증 정보를 관리하는 UserDetailsService 인터페이스를 직접 커

curiousjinan.tistory.com

 

반응형

'Spring > Spring Security' 카테고리의 다른 글

Spring Boot 3.1 & Spring Security 6: 로그인 & 메인 페이지 컨트롤러 (9편)  (0) 2023.08.08
Spring Boot 3 & Security 6 시리즈: UserDetailsService, DTO 작성하기 (8편)  (0) 2023.08.07
Spring Boot 3 & Security 6 시리즈: JWT 검증 인터셉터 작성하기 (6편)  (0) 2023.08.07
Spring Boot 3 & Security 6 시리즈: AuthenticationProvider, 인증 핸들러 구현하기 (5편)  (0) 2023.08.07
Spring Boot 3 & Security 6 시리즈: JWT 인증 필터 JwtAuthorizationFilter 작성(4편)  (0) 2023.08.07
'Spring/Spring Security' 카테고리의 다른 글
  • Spring Boot 3.1 & Spring Security 6: 로그인 & 메인 페이지 컨트롤러 (9편)
  • Spring Boot 3 & Security 6 시리즈: UserDetailsService, DTO 작성하기 (8편)
  • Spring Boot 3 & Security 6 시리즈: JWT 검증 인터셉터 작성하기 (6편)
  • Spring Boot 3 & Security 6 시리즈: AuthenticationProvider, 인증 핸들러 구현하기 (5편)
Stark97
Stark97
문의사항 또는 커피챗 요청은 링크드인 메신저를 보내주세요! : https://www.linkedin.com/in/writedev/
  • Stark97
    오늘도 개발중입니다
    Stark97
  • 전체
    오늘
    어제
    • 분류 전체보기 (249)
      • 개발지식 (20)
        • 스레드(Thread) (8)
        • WEB, DB, GIT (3)
        • 디자인패턴 (8)
      • JAVA (21)
      • Spring (88)
        • Spring 기초 지식 (35)
        • Spring 설정 (6)
        • JPA (7)
        • Spring Security (17)
        • Spring에서 Java 활용하기 (8)
        • 테스트 코드 (15)
      • 아키텍처 (6)
      • MSA (15)
      • DDD (12)
      • gRPC (9)
      • Apache Kafka (18)
      • DevOps (23)
        • nGrinder (4)
        • Docker (1)
        • k8s (1)
        • 테라폼(Terraform) (12)
      • AWS (32)
        • ECS, ECR (14)
        • EC2 (2)
        • CodePipeline, CICD (8)
        • SNS, SQS (5)
        • RDS (2)
      • notion&obsidian (3)
      • 채팅 서비스 (1)
      • AI 탐험대 (1)
      • 팀 Pulse (0)
  • 링크

    • notion기록
    • 깃허브
    • 링크드인
  • hELLO· Designed By정상우.v4.10.0
Stark97
Spring Boot 3 & Security 6 시리즈: JWT Util 클래스 작성 (7편)
상단으로

티스토리툴바