JWT 정리

블랑·2023년 7월 3일
0

Daily Study

목록 보기
5/5

참고할 만한 추가적인 게시글들 :
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

Gradle

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 Util 클래스 작성

JWT 생성

  • JWT를 생성하는 기능은 사용자 인증 후 유효한 토큰을 발급하는 데 사용된다.
  • JWT는 헤더, 페이로드, 서명으로 구성된다.
  1. 헤더 : JWT의 타입 및 서명 알고리즘 등이 포함
  2. 페이로드 : 클레임(claim) 정보 포함 (토큰에 저장할 사용자 정보 및 기타 데이터)
  3. 서명 : 헤더와 페이로드 기반으로 생성되며, 토큰의 무결성 보장

JWT 검증

  • JWT 검증은 클라이언트로부터 받은 토큰의 유효성을 확인하는 기능이다.
  • 검증 과정에서 토큰의 서명을 검증하여 토큰의 무결성을 보장한다
  • 유효한 토큰인 경우 클레임 정보를 추출하여 필요한 처리를 수행할 수 있다.

이 부분에서 추가적으로 정리한 부분을 살펴보자.

  1. 서명 : JWT는 서버의 시크릿 키를 사용하여 서명되었다.
    검증 과정에서는 수신한 JWT의 서명을 해당 시크릿 키를 사용하여 검증한다. 이를 통해 JWT가 변조되지 않았는지 확인할 수 있다.

  2. 만료 시간 확인 : JWT에는 만료 시간이 포함되어 있다.
    검증 과정에서는 현재 시간과 JWT의 만료 시간을 비교하여 JWT가 만료되었는지 확인한다. 이때 시간이 만료되었다면, 유효하지 않은 토큰인 것이다.

  3. 추가적인 클레임 검증 : 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");
    }
}

메시지 바디 vs 헤더

.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;
        }
    }
}

인증 기능 구현

JWT 검증 필터 추가

보안 설정 구성

++ RespnseEntity

https://thalals.tistory.com/268

profile
안녕하세요.

0개의 댓글