[Spring] JWT 토큰 생성과 검증

HOJUN·2024년 6월 22일

Backend - Spring

목록 보기
34/34

JWT Token

JWT 토큰을 io로 생성해보았으니 직접 코드로 생성해보자

Service 레이어에서 들어갈 정보를 포함하는 토큰을 생성하는 메소드와 이를 검증할 수 있는 메소드를 작성한다.

public String create(
            Map<String, Object> claims,
            LocalDateTime expireAt
    ){
        var key = Keys.hmacShaKeyFor(secretKey.getBytes());

        //LocalDateTime -> Date로 맞춰주는 코드
        var _expireAt = Date.from(expireAt.atZone(ZoneId.systemDefault()).toInstant());

        return Jwts.builder()
                .signWith(key, SignatureAlgorithm.HS256)
                .setClaims(claims)
                .setExpiration(_expireAt)
                .compact();
    }

claims는 여러 데이터가 들어갈 수 있는 Map으로 이전 포스트에서 작성했던 여러 정보가 포함된다.
expireAt은 언제 만료될지 커스텀할 수 있는 시간이다.

Keys.hmacShaKeyFor(secretKey.getBytes());

해당 코드를 통해서 Byte형식의 key를 생성한다.
expiredAt은 현재시간인 LocalDateTime이므로 Date타입으로 바꿔서 _expireAt으로 변경한다.

Jwts토큰을 빌더패턴을 사용해 key, 암호화알고리즘, claims, 만료시간을 지정한다.

public void validation(String token){
        var key = Keys.hmacShaKeyFor(secretKey.getBytes());

        var parser = Jwts.parserBuilder()
                .setSigningKey(key)
                .build();
        
        try{
            var result = parser.parseClaimsJws(token);
            result.getBody().entrySet().forEach(value-> {
                log.info("key : {}, value : {}", value.getKey(), value.getValue());
            });
        } catch (Exception e){
            if(e instanceof SignatureException){
                throw new RuntimeException("JWT Token Not Valid Exception");
            } else if (e instanceof ExpiredJwtException) {
                throw new RuntimeException("JWT Token Expired Exception");
            } else {
                throw new RuntimeException("JWT Token Valid Exception");
            }
        }
    }

토큰을 검증하는 코드는 다음과 같은데, try - catch문을 참고하면 토큰이 유효한지, 만료됐는지 등의 예외가 발생하는데 해당 예외를 제외한 나머지는 아직 중요하지 않으므로 두개만 잡도록 하자

비밀키를 복호화하고 발행된 토큰을 이용해서 서명된 JWS(Json Web Signature)를 검증해 result에 클레임셋을 반환한다.

var parser = Jwts.parserBuilder()
                .setSigningKey(key)
                .build();
var result = parser.parseClaimsJws(token);

클레임셋에서 우리가 궁금한 것은 Body부분이기 때문에 getBody로 클레임셋에 존재하는 데이터를 로깅해보자.

result.getBody().entrySet().forEach(value-> {
        log.info("key : {}, value : {}", value.getKey(), value.getValue());
        });

그렇게 되면 토큰과 비밀키가 필요하므로 예외가 발생하는 상황을 만들 수 있다.

private static String secretKey = "";

key에 들어갈 secretKey는 클래스 내에 선언한다. 빈 문자열에 사용자가 정할 Secret Code를 적으면 된다.

Controller코드를 작성하지 않고 테스트 코드로 검증해보자.

@Test
void tokenCreate(){
	var claims = new HashMap<String, Object>();
	claims.put("user_id", 999);

	var expired_at = LocalDateTime.now().plusSeconds(20);

	var jwtToken = jwtService.create(claims, expired_at);

	System.out.println(jwtToken);
}

claims에 "user_id" : 999와 만료시간인 현재로부터 20초 뒤의 시간만 담아서 토큰을 발행해보자.

@Test
void tokenValidation(){

	var token = "토큰 입력칸";

	jwtService.validation(token);
}

토큰 입력칸에 발생된 토큰을 넣고 검증하면 된다.
20초 뒤에 토큰이 만료되므로 빠르게 검증해야 한다.


토큰을 발행했고 dot 연산자로 이루어진 것을 볼 수 있다.


20초 내에 검증했고 user_id가 999인 것과 시간을 볼 수 있다.
이후 시간이 지나서 검증을 시도했다면

예외가 발생하는 것을 볼 수 있다.
이는 토큰을 잘못 입력해도 예외를 던진다.

20분의 만료시간을 갖는 토큰을 가지고 직접 디코딩해보자.


해당 토큰을 jwt.io encoded 영역에 붙여넣으면

시간은 살짝 다르지만 잘 변환이 된 것을 볼 수 있다.
해당 토큰의 비밀키는 fastcampuscourse4chapter01jwtauthentication인데, 4를 3으로 바꾸면 토큰의 SIGNATURE부분도 바뀐다.

다시 비밀키를 설정하고 바뀐 토큰을 넣으면

Signature가 맞지 않다는 메세지로 검증 가능하므로 해당 토큰은 변조된 토큰인 것을 확인할 수 있다.

0개의 댓글