JWT 인증의 원리와 필요성

김형진·2023년 7월 18일
2

JWT란?

Json Web Token 의 약자로, 하나의 토큰안에 인증을 위한 정보가 모두 두어 사용자를 인증하는 방식의 수단이다. 인증 방식은 다음과 같다.

  1. 사용자가 서버에 로그인 요청한다. (ID, PW)
  2. 서버는 사용자의 요청을 확인하고 올바른 유저라면 유저 정보가 담긴 token을 발급하여 사용자에게 전달한다.
  3. 사용자는 리턴받은 토큰을 local에 저장하고 있다가 api요청마다 해당 토큰을 요청 헤더에 담아 서버에 전달한다.
  4. 서버는 토큰 정보를 확인하여 인허가된 유저인지 확인하고 api를 처리한다.
  5. 토큰 만료 시 사용자는 서버에 토큰 재발급을 요청한다.

엥? 어떻게 그게 인증이 되지?

흐름은 알겠는데 어떻게 JWT자체로 사용자 인증이 가능한지 의문이었다.
사용자가 계속 가지고 있는 토큰만으로 인증이 가능하다면,
위변조를 통해 얼마든지 사용자 인증을 받아낼 수 있지 않을까?

JWT가 어떻게 생성됐는지, 그리고 이를 통해 어떻게 유효한 사용자 요청인지 확인하는 방법을 알면 의문을 해결할 수 있다.


우선 글로만 설명하면 JWT를 생성하는 과정과, 사용자의 JWT를 확인하는 과정은 다음과 같다.
[생성]
  1. 서버에서는 자신만 알고 있는 시크릿 키를 가지고 있다.
  2. 헤더와 페이로드를 각각 base64Url로 인코딩한다.
  3. 헤더와 페이로드 문자열을 합친다
  4. 합쳐진 문자열과 서버의 시크릿 키를 조합하여 서명을 생성한다. (해시 알고리즘 사용)
  5. 헤더, 페이로드, 서명을 .으로 연결하여 JWT를 생성하고 이를 사용자한테 건네준다.
[확인]
  1. 클라이언트에게 넘겨받은 JWT에서 헤더와 페이로드 정보를 가져온다.
  2. 서버는 자신이 가지고 있는 키를 이용하여 [생성]에서의 2,3,4번 과정을 거쳐 새로운 서명을 생성한다.
  3. 사용자가 넘긴 서명과 새로 생성된 서명을 비교하여 같은지 확인한다.

이를 자바 코드로 구현하면 다음과 같다.
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class Main {
    public static void main(String[] args) {
        String header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
        String payload = "{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"iat\":1516239022}";

        try {
            String base64UrlHeader = base64UrlEncode(header);
            String base64UrlPayload = base64UrlEncode(payload);

            String data = base64UrlHeader + "." + base64UrlPayload;
            String secretKey = "my-secret-key";

            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256_HMAC.init(secret_key);

            byte[] hash_byte = sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
            String signature = Base64.getUrlEncoder().encodeToString(hash_byte);

            System.out.println("Signature: " + signature);

        } catch (Exception e) {
            System.out.println("Error");
        }
    }

    private static String base64UrlEncode(String data) {
        String encodedString = Base64.getUrlEncoder().encodeToString(data.getBytes(StandardCharsets.UTF_8));
        return encodedString;
    }
}

핵심은 서버만 알고 있는 시크릿키와 조합하는 것이다.
만약 사용자가 토큰의 만료기간을 늘리고자 페이로드의 exp값을 조작하여 base64Url로 다시 인코딩하더라도, 서버에서는 변경된 페이로드와 자신의 시크릿키를 조합하여 생성된 새로운 서명이 사용자가 기존에 가지고 있던 서명과 불일치하기 때문에 토큰이 임의로 조작되었음을 알 수 있다.

Session  vs  JWT

요새는 Session 대신 JWT를 많이 사용하는 추세이다.
본인도 개인프로젝트에서 혼자 프론트와 백엔드를 오가는 고생을 하며 구글링을 하던 와중 JWT를 많이 접하고 필요성을 인지하게 되어 포스팅을 하게 되었다.
그래서 JWT를 적용하기 전 적용해야 하는 이유와 원리를 명확하게 하기 위해 글을 쓰게 되었다.

다음은 Session 대신 JWT를 많이 사용하는 이유이다.

분산 시스템에 적합

세션 기반의 인증은 사용자 인증 정보를 서버에 저장하는 방식이므로, 여러 서버 사이에서 세션 정보를 공유해야 하는 분산 시스템에서는 문제가 발생할 수 있다. 반면에 JWT는 클라이언트 측에 저장되는 토큰 기반 인증 방식이므로, 이런 환경에서도 쉽게 사용할 수 있다.

확장성

세션은 사용자가 많아질수록 서버의 부하가 증가하는 문제가 있다. 반면에 JWT는 클라이언트 측에서 인증 정보를 저장하므로, 서버 부하를 크게 줄일 수 있다. 이는 특히 대규모 시스템에서 중요한 장점이 된다.

모바일 환경 적합성

모바일 애플리케이션에서는 세션을 관리하는 것이 어려울 수 있다. 모바일 애플리케이션은 브라우저가 아니기 때문에 쿠키를 사용한 세션 관리를 그대로 사용하기 어렵다. 반면에 JWT는 HTTP 헤더에 포함되어 전송되므로, 모바일 환경에서도 쉽게 사용할 수 있다.

자체 포함적인 정보

JWT는 자체적으로 필요한 모든 정보를 가지고 있다. JWT 토큰 하나로 사용자의 신원, 로그인 정보, 권한 등을 알 수 있다.

크로스 도메인

세션 쿠키는 동일 출처 정책(Same-Origin Policy)에 의해 한 도메인에서만 사용할 수 있지만, JWT는 크로스 도메인이 가능하므로, 서로 다른 도메인 간에도 인증 정보를 쉽게 전달할 수 있다.

RESTful 서비스

최근 웹 서비스는 REST 아키텍처를 따르는 경향이 있다. REST는 상태 정보를 갖지 않는(Stateless) 특성이 있으므로, JWT와 같은 토큰 기반 인증 방식과 잘 어울린다.

profile
히히

4개의 댓글

comment-user-thumbnail
2023년 7월 18일

너무 좋은 글이네요. 공유해주셔서 감사합니다.

1개의 답글
comment-user-thumbnail
2023년 7월 18일

훌륭한 글이네요. 감사합니다.

1개의 답글