[Spring Boot ] JWT 개념과 기본 구현

도즈·2025년 3월 16일

spring

목록 보기
10/12

JWT 개념과 기본 구현

📘 JWT (JSON Web Token)

  • JWT (JSON Web Token)는 정보를 안전하게 전송하기 위한 표준 방법 중 하나이다.
  • 인증된 사용자가 데이터를 안전하게 전송하고, 정보를 검증할 때 사용된다.
  • JWT는 json 객체를 사용하여 정보를 저장하고, 디지털 서명을 통해 검증된다

JWT 자체는 인증/인가와 관련이 없다 → 정보를 안전하게 전송하기 위한 방법 중 하나이다.


✅ JWT 구성요소

JWT는 3가지 부분으로 구성

1️⃣ Header

  • JWT 토큰 타입과 토큰을 검증할 때 사용하는 signature인 해싱 알고리즘 정보가 담겨져 있다.
    {
    	"alg":"HS256",
    	"typ": "JWT"
    }

2️⃣ Payload

  • 실제로 전송할 데이터가 담긴 부분이다.
  • 클레임(cliam)이라고 불리는 정보들이 포함된다 📢 클레임은 3가지 유형으로 나뉜다.
    1. Registered claims
      • 특정한 정보를 제공하기 위해 사전에 정의된 클레임
      • 발행자(issuer), 만료 시간(expiration time), 주체(subject) 등이 있다.
    2. Public claims
      • 사용자 정의 클레임이다.
      • 충돌을 피하기 위해 URI 형식으로 작성된다.
    3. private claims
      • 애플리케이션에서 사용할 수 있는 클레임이다.

      • 사용자 정의 정보를 포함할 수 있는 클레임으로, 서버와 클라이언트 간의 임의적인 정보를 주고받을 수 있다.

        {
          "id": "user123",
          "name": "John Doe",
          "company" : "test",
          "admin": true
        }

3️⃣ Signature

  • Header와 Payload의 내용을 인코딩하고, 비밀키를 사용하여 서명한다.
  • 이 서명은 메시지가 변경되지 않았음을 확인한다.

✅ JWT 작동 방식

1️⃣ 생성 

  • 사용자가 로그인하면 서버는 JWT를 생성하여 사용자에게 반환환다.
  • 이 JWT는 클라이언트의 저장소 (일반적으로는 로컬 스토리지나 쿠키)에 저장된다.

2️⃣ 전송

  • 클라이언트는 JWT를 HTTP 요청의 헤더나 URL 매개변수로 전송하여 서버에 전달한다.

3️⃣ 검증

  • 서버는 JWT의 서명을 통해 해당 토큰이 유효한지 검증하고, 클라이언트가 요청한 작업을 수행한다.
  • 서명이 일치하지 않거나 JWT가 만료된 경우 서버는 요청을 거부할 수 있다.



📘 JWT 구현

✅ JwtTokenizer 생성( JWT 토큰 생성 )

JWT(JSON Web Token)을 생성하고 검증하는 유틸리티 클래스

1-1 yml 파일에 Access Token의 서명을 생성할 때 사용되는 값 추가

jwt:
secretKey: 12345678901234567890123456789012
refreshKey: 12345678901234567890123456789012
  • yml 파일에서 사용할 시크릿 키 알고리즘 값을 넣어줬다.

1-2 accessSecret, refreshSecret 키 기본 정보 설정

@Component
@Slf4j
public class JwtTokenizer {

    private final byte[] accessSecret;
    private final byte[] refreshSecret;

    //토큰 만료 시간이 달라지면 안되기 때문에 static으로 사용해야 한다.
    public static final Long ACCESS_TOKEN_EXPIRE_COUNT = 5 * 60 * 60 * 1000L;
    // Access Token 유효기간: 5시간 (5시간 * 60분 * 60초 * 1000ms)
    public static final Long REFRESH_TOKEN_EXPIRE_COUNT = 24 * 60 * 60 * 1000L;
    // Refresh Token 유효기간: 1일 (24시간 * 60분 * 60초 * 1000ms)

    public JwtTokenizer(@Value("${jwt.secretKey}") String accessSecret, @Value("${jwt.refreshKey}") String refreshSecret) {
        this.accessSecret = accessSecret.getBytes(StandardCharsets.UTF_8);
        this.refreshSecret = refreshSecret.getBytes(StandardCharsets.UTF_8);
    }

1-3 HMAC SHA 알고리즘을 적용하여 서명(Signing)에 사용할 키 생성 하는 역할

  private static Key getSigningKey(byte[] secretKey){
        return Keys.hmacShaKeyFor(secretKey);
    }

1-4  토큰 생성 메서드

    //accessToken 생성
    public String createToken(Long id, String email,byte[] secretKey,Long expire){

        //이 토큰의 주인이 누구인지 나타내는 클레임
        //고유한 식별자 값 저장
        Claims claims = Jwts.claims().setSubject(email);

        claims.put("userId",id);
        claims.put("email",email);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date())
                .setExpiration(new Date(new Date().getTime()+expire))
                .signWith(getSigningKey(secretKey))
                .compact();

    }
  • Jwts.builder()
    • JWT를 생성하기 위한 빌더 객체 반환
    • 여기서 JWT의 클레임(claim), 발급 시간, 만료 시간, 서명 등을 설정할 수 있다.
  • setClaims
    • JWT에 포함될 클레임을 설정
    • 클레임은 사용자 정보를 포함한다.
  • setIssuedAt
    • JWT 발급된 시간
  • setExpiration
    • JWT 만료 시간
  • signWith(getSigningKey(secretKey))
    • WT의 서명(Signature)을 설정
    • secretKey를 이용해 HMAC-SHA 알고리즘을 사용하여 JWT에 서명을 추가
    • 이 서명은 JWT가 변조되지 않았는지 확인하는 데 사용
  • compact()
    • JWT를 문자열(String)로 최종 변환
    • 이 문자열은 헤더(Header), 페이로드(Payload), 서명(Signature) 부분으로 구성된 JWT 토큰

결과로 아래 같은 jwt 토큰 문자열이 생성

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI.. . .~ 

1-5  토큰 생성 메서드를 통해 access, refresh 토큰 생성

    //access token 생성
    public String createAccessToken(Long id, String email){
        return createToken(id, email, accessSecret,ACCESS_TOKEN_EXPIRE_COUNT);
    }
    

    //refresh token 
    public String refreshAccessToken(Long id, String email){
        return createToken(id, email, refreshSecret, REFRESH_TOKEN_EXPIRE_COUNT);
    }
    
  • 토큰 생성 메서드를 통해서 access, refresh 토큰을 발급한다.

1-6 토큰에서 데이터(Claims) 받는 메서드

  • JWT 토큰을 파싱하여 토큰 내의 Claims(토큰에 저장된 데이터)를 추출하는 메서드
   //받은 토큰에서 데이터 받는 메서드
    public Claims parseToken(String token, byte[] secretKey){
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey(secretKey))
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
  • Jwts.parserBuilder()
    • jwt를 파싱하기 위한 빌더 객체를 생성
    • jwt를 구문 분석(파싱)하는데 필요한 설정을 할 수 있도록 도와준다.
  • setSigningKey(getSigningKey(secretKey))
    • 서명 검증을 위해 사용하는 비밀키를 설정한다.
  • build()
    • build()는 parserBuilder()로 설정한 값을 바탕으로 실제 파서(Parser) 객체를 생성하는 메서드
      • 위에서는 setSigningKey로 서명 검증을 위한 비밀키를 설정해주었다.
    • build()는 단순히 파서 객체를 만들어주는 역할이므로 파싱 자체를 수행하지 않는다.
  • parseClaimsJws(token)
    • JWT 토큰(token)을 실제로 파싱하여, JWT의 헤더(header)와 페이로드(payload)를 추출한다.
    • 파싱 후에는 JWT의 헤더, 페이로드, 서명 등을 확인하여 JWT가 유효한지 검사한다.
  • getBody()
    • 파싱된 JWT 토큰에서 Claims 객체를 추출하는 메서드
    • Claims 객체는 JWT의 페이로드 부분에 담겨 있는 유저 정보사용자 지정 정보 등을 포함하고 있다.
      • 예를 들어, 사용자 ID, 이메일, 역할(role) 등을 담고 있을 수 있다.

1-7  parseToken 메서드를 통해 데이터 추출하기

public Long getUserIdFromToken(String token) {
	String[] tokenArr = token.split(" ");
	token = tokenArr[1];
	Claims claims = parseToken(token, accessSecret);
	return Long.valueOf((Integer)claims.get("userId"));
	}
  • 요청으로 오는 토큰은 Beaer를 포함하고 있을 것이기 때문에, 공백을 통해서 뒤에 있는 토큰 값만 사용할 수 있게 해준다.


1-8 Access, Refresh 토큰에서 데이터 추출하기

    public Claims parseAccessToken(String accessToken){
        return parseToken(accessToken, accessSecret);
    }

    public Claims parseRefreshToken(String refreshToken){
        return parseToken(refreshToken,accessSecret);
    }

JWT Test

    public static void main(String[] args) {

	// 토큰 생성을 위한 JwtTokenizer 인스턴스 생성
        JwtTokenizer jwtTokenizer = new JwtTokenizer(
                "12345678901234567890123456789012"
                ,
                "12345678901234567890123456789012");
	
	// AccessToken 생성 예시
        String accessToken = jwtTokenizer.createAccessToken(
                1L,
                "test@example.com");

		
		// AccessToken 파싱 예시
        Claims accessTokenClaims = jwtTokenizer.parseAccessToken(accessToken);
        log.info("Access Token Claims: {}"
                , accessTokenClaims);

       
       // RefreshToken 파싱 예시
        Claims refreshTokenClaims = jwtTokenizer.parseRefreshToken(refreshToken);
        log.info("Refresh Token Claims: {}"
                , refreshTokenClaims);
                
   }
profile
도즈의 개발이야기

0개의 댓글