본문 바로가기
Spring

Java Spring JWT 생성 및 검증 로직 구현

by wadekang 2024. 1. 11.

 

이 글은 https://wadekang.tistory.com/70 포스트에 이어서 작성되었습니다. 해당 포스트에서 프로젝트 설정들을 확인하세요.

 

Java Spring에서 JWKS(JSON Web Key Set) API 구현

버전 정보 java: openjdk 17.0.8 spring: Spring Boot 3.2.1 nimbus-jose-jwt: 9.31 프로젝트 생성 start.spring.io 에서 위와 같이 프로젝트 생성 JWK & JWKS JWK (JSON Web Key) Json Web Key (JWK)는 JSON 형식으로 표현된 공개키 또는

wadekang.tistory.com

 

JWT

JWT는 JSON Web Token의 약어로, 웹에서 정보를 안전하게 전달하기 위한 토큰 형식 중의 하나

주로 사용자 인증 정보를 포함하고 안전하게 전달하는 데에 활용

 

JWT의 구조는 세 부분으로 나뉨

  1. Header (헤더): JWT 타입과 사용하는 해싱 알고리즘을 지정. Base64로 인코딩 되어 JWT의 첫 부분을 구성
  2. Payload (페이로드): Claim(클레임)이라 불리는 정보들이 포함되어 있음. 클레임은 사용자에 대한 정보나 토큰 자체에 대한 메타데이터 등을 담고 있음. 페이로드 역시 Base64로 인코딩되어 JWT의 두 번째 부분을 구성
    • JWT의 'sub' 클레임은 주로 토큰이 나타내는 주체(Subject)를 식별하는 데 사용됨. 이 값은 일반적으로 사용자의 고유 식별자(user identifier)나 사용자 이름(username) 등으로 설정
  3. Signature (서명): 헤더와 페이로드의 내용을 인코딩하고, 비밀 키를 사용하여 서명된 값이 포함. 서명은 헤더에서 지정한 해싱 알고리즘에 따라 생성되며, 서버에서만 알고 있는 비밀 키를 사용하여 만들어짐

JWT는 다음과 같은 특징이 있음

  • Compact: JWT는 URL, POST 매개변수 및 HTTP 헤더를 통해 전송할 수 있을 정도로 간결하게 설계
  • Self-Contained: JWT는 필요한 모든 정보를 자체적으로 포함하고 있어, 별도의 인프라나 데이터베이스 조회 없이 검증 가능
  • Extensible: 헤더와 페이로드에는 필수적인 클레임 외에도 사용자 정의 클레임을 추가할 수 있음

JWT는 주로 웹 애플리케이션에서 사용자 인증 및 권한 부여에 활용되며, OAuth 2.0과 OpenID Connect 등의 프로토콜에서도 널리 사용됨. 클라이언트가 서버로부터 받은 JWT를 저장하고, 나중에 필요할 때 서버에 전송하여 인증 및 권한 확인을 수행할 수 있음

 

JWT 생성 및 검증 구현

 

JWT 관련 라이브러리 추가

dependencies {

	...
	implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.12.3'
}

 

JWT 관련 로직을 수행하는 JwtManager 구현

@Slf4j  
@Component  
public class JwtManager {  
  
    private final KeyPair keyPair;
    private final JWKSet jwks;
    private static final String ISSUER = "com.example.auth";
    private static final long EXPIRATION_TIME = 3600000; // 1시간
  
    // ... 컨스트럭터와 기타 다른 메서드 생략 (앞의 문서에서 구현한 부분)

    public String createJwt(Long userId, UserVO userVO) {  
        long millis = System.currentTimeMillis();  
        Date now = new Date(millis);  
        Date expiration = new Date(millis + EXPIRATION_TIME);  
  
        return "Bearer " + Jwts.builder()  
                .header()  
                    .type("JWT") // 헤더에 타입 지정
                .and()  
                .subject(String.valueOf(userId))  
                .claims(userToMap(userVO)) // 토큰에 담을 Claim 정보
                .issuedAt(now) // 토큰 생성 시간
                .expiration(expiration) // 토큰 만료 시간
                .issuer(ISSUER) // issuer
                .signWith(keyPair.getPrivate()) // 비밀키로 서명
                .compact();  
    }  
  
    public Claims validateJwt(String jwt) {  
        // JWT가 유효하지 않을 경우 JwtException이 발생
        // 파싱된 Claim에서 유저, 권한 등의 정보를 가져와 서비스 로직에서 사용할 수 있음
        Claims claims = Jwts.parser()  
                .verifyWith(keyPair.getPublic()) // 공개키로 검증
                .build()  
                .parseSignedClaims(jwt)
                .getPayload();  

        log.info("claims: {}", claims);  

		return claims;
    }  
  
    private Map<String, Object> userToMap(UserVO userVO) {  
        Map<String, Object> userMap = new HashMap<>();  
  
        userMap.put("userId", userVO.getUserId());  
        userMap.put("email", userVO.getEmail());  
        userMap.put("userName", userVO.getUserName());  
        userMap.put("role", userVO.getRole());  
  
        return userMap;  
    }  
}

 

해당 코드를 통해 생성한 JWT를 디코딩하면 다음과 같은 구조로 구성되어 있음

 

Header

{
  "typ": "JWT",
  "alg": "RS256"
}

 

Payload

{
  "sub": "1",
  "role": "ROLE_ADMIN",
  "userName": "system",
  "userId": 1,
  "email": "auth@example.com",
  "iat": 1704952289,
  "exp": 1704955889,
  "iss": "com.example.auth"
}

 

Signature

RSASHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  public or private key
)

 

 

댓글