JWT 기반의 무상태 인증 시스템 설계 및 구현

JWT의 핵심 개념

JSON Web Token(JWT)은 애플리케이션 간에 안전하게 정보를 전달하기 위한 표준 형식입니다. 주로 사용자 인증, 정보 공유 및 권한 부여 등에 활용되며, 서버 상태를 유지하지 않는 무상태 아키텍처에서 적합합니다.

JWT의 구성 요소

  • 헤더(Header): 토큰 유형과 서명 알고리즘을 정의합니다. 예: {"typ": "JWT", "alg": "RS256"}
  • 페이로드(Payload): 실제 데이터가 포함된 영역으로, 다음 세 가지 종류의 클레임을 포함할 수 있습니다:
    • 기본 클레임 (예: iss, exp, sub)
    • 공개 클레임 (개발자가 정의하는 커스텀 값)
    • 비공개 클레임 (특정 서비스 간에 공유되는 내부 정보)
  • 서명(Signature): 헤더와 페이로드를 결합하여 생성되며, 메시지의 무결성을 보장합니다. 서명은 비밀키로 생성되고, 공개키로 검증됩니다.

키 쌍 생성 및 토큰 발급

암호화를 위해 RSA 기반의 키 쌍을 생성하는 예제 코드입니다.

public class KeyPairGenerator {
    public static void main(String[] args) throws JoseException {
        RsaJsonWebKey key = RsaJwkGenerator.generateJwk(2048);
        System.out.println("공개키:\n" + key.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY));
        System.out.println("비밀키:\n" + key.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE));
    }
}

JWT 발행 도구 클래스

사용자 식별자와 이름을 기반으로 JWT를 생성하고 검증하는 유틸리티 클래스입니다.

public class TokenService {
    private static final String PRIVATE_KEY_JWK = "{...}"; // 비밀키 문자열
    private static final String PUBLIC_KEY_JWK = "{...}";  // 공개키 문자열

    public static String generateToken(Long userId, String userName) throws Exception {
        JwtClaims claims = new JwtClaims();
        claims.setIssuer("myapp");
        claims.setAudience("api");
        claims.setExpirationTimeMinutesInTheFuture(1440); // 24시간
        claims.setGeneratedJwtId();
        claims.setIssuedAtToNow();
        claims.setNotBeforeMinutesInThePast(1);
        claims.setSubject("user-auth");
        claims.setClaim("id", userId);
        claims.setClaim("name", userName);

        JsonWebSignature jws = new JsonWebSignature();
        jws.setPayload(claims.toJson());
        jws.setKey(new RsaJsonWebKey(JsonUtil.parseJson(PRIVATE_KEY_JWK)).getPrivateKey());
        jws.setKeyIdHeaderValue("key-1");
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);

        return jws.getCompactSerialization();
    }

    public static Map<String, Object> parseToken(String token) {
        try {
            PublicKey publicKey = new RsaJsonWebKey(JsonUtil.parseJson(PUBLIC_KEY_JWK)).getPublicKey();
            JwtConsumer consumer = new JwtConsumerBuilder()
                .setRequireExpirationTime()
                .setAllowedClockSkewInSeconds(30)
                .setRequireSubject()
                .setExpectedIssuer("myapp")
                .setExpectedAudience("api")
                .setVerificationKey(publicKey)
                .setJwsAlgorithmConstraints(
                    new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST, AlgorithmIdentifiers.RSA_USING_SHA256))
                .build();

            JwtClaims claims = consumer.processToClaims(token);
            return claims.getClaimsMap();
        } catch (Exception e) {
            return new HashMap<>();
        }
    }
}

사용 예시

String token = TokenService.generateToken(1001L, "alice");
Map<String, Object> payload = TokenService.parseToken(token);
System.out.println(payload.get("id"));     // 출력: 1001
System.out.println(payload.get("name"));   // 출력: alice

태그: jwt RSA JSON Web Token oauth2 Security

7월 29일 13:33에 게시됨