728x90
반응형

이음선 프로젝트에서는 사용자 경험을 해치지 않으면서도 인증을 효과적으로 처리하는 것이 주요 요구사항 중 하나였습니다. 여기서 사용자 경험이란, 사용자가 자주 로그인을 요구받지 않는 환경을 말합니다.

하지만 세션 기반 인증을 사용하면, 서버의 부하가 과중될 가능성이 있었습니다. OpenAI API를 호출할 때 질문의 답변 생성과 위험도 판단을 위해 이미 두 개의 쓰레드가 사용되고, 여기에 세션 캐시와 관련 쓰레드까지 추가된다면 서버 자원이 부족해질 위험이 컸습니다.

이에 따라 JWT(Json Web Token)를 활용한 인증 방식을 도입하기로 결정했습니다. 이번 글에서는 JWT의 생성, 검증, 적용 과정과 Spring Boot에서 이를 구현하는 방법에 대해 설명하겠습니다.


JWT 설치

Gradle를 이용해서 간단하게 라이브러리를 설치할 수 있습니다.

implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'

JWT 생성 코드

JWT를 사용하려면 먼저 토큰을 생성하는 로직이 필요합니다. 아래는 JWT 생성 코드입니다

private String createToken(CreateTokenDto tokenDto, Date tokenExpiresIn) {
    String token = Jwts.builder()
            .subject(SUBJECT_ACCESS)
            .claim(USER_UUID, tokenDto.userId())
            .issuedAt(new Date())
            .expiration(tokenExpiresIn)
            .signWith(key)
            .compact();
    return TOKEN_PREFIX + token;
}

위 코드에서 주요 구성 요소를 하나씩 살펴보겠습니다:

  • subject: 토큰의 용도를 나타내는 문자열입니다. 예: SUBJECT_ACCESS.
  • claim: JWT에 포함되는 사용자 데이터입니다. 예를 들어, 사용자의 이메일을 저장할 수 있습니다. 다만, 이메일은 탈취될 경우 개인정보 유출 우려가 있으므로 UUID나 ID 값을 사용하는 것이 좋습니다.
  • issuedAt: 토큰 생성 시각입니다.
  • expiration: 토큰 만료 시각입니다.
  • signWith: 토큰을 서명할 키입니다.

추가적으로, Token Prefix는 토큰 유형을 명시하는 문자열로, JWT를 사용할 경우 Bearer를 설정합니다.


키 생성 방법

JWT의 서명을 위해서는 키 생성이 필요합니다. 아래는 키 생성 코드입니다

import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

@Value("${spring.jwt.secretKey}")
private String secretKey;
private SecretKey key;

@PostConstruct
private void initializeKey() {
    this.key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretKey));
}

주요 설명

  • secretKey: 랜덤한 문자열을 받은 후에 그 값을 byte 배열로 변환 후, Base64URL로 암호화한다. 그 후에 다시 복호를 진행한 후, HMAC-SHA 알고리즘을 사용하여 SecretKey를 만든다.
    근데 이렇게 된다면 사실 그냥 랜덤한 문자열을 바로 복호하여 Byte배열로 만든 후, HMAC-SHA 알고리즘을 사용하여 SecretKey를 만들면 되는게 아닌가? 해서 그렇게 진행하였다.
더보기

이때 랜덤한 문자열을 OS에서 자동으로 만들어준다. 터미널에 다음과 같은 명령어를 입력하면 된다.

- openssl rand -hex 64

맨 뒤 64는 문자 길이를 말한다.

  • initializeKey: io.jsonwebtoken 라이브러리를 활용해 Base64 URL로 인코딩된 키를 복호하고, HMAC-SHA 알고리즘으로 서명을 위한 키를 생성합니다.

JWT 검증 코드

JWT를 클라이언트에 제공한 뒤, 서버는 클라이언트로부터 받은 토큰을 검증해야 합니다. 검증 로직은 아래와 같습니다

private Jws<Claims> verify(String jwt, String type) {
    return Jwts.parser()
           .verifyWith(key)
           .requireSubject(type)
           .build()
           .parseSignedClaims(jwt);
}

주요 구성 요소

  • verifyWith: 토큰 생성 시 사용한 키를 입력합니다.
  • requireSubject: 토큰의 용도를 확인합니다.
  • parseSignedClaims: 클라이언트로부터 받은 토큰을 해석하여 유효성을 검증합니다.

Claims 데이터 추출

JWT에서 필요한 데이터를 추출하는 방법은 다음과 같습니다

public String getAccessTokenUserPk(String token) {
    return verify(token, SUBJECT_ACCESS)
            .getPayload()
            .get(USER_UUID)
            .toString();
}

설명

  • verify: 토큰 검증을 수행하는 함수입니다.
  • getPayload: 검증된 토큰의 데이터를 반환합니다.
  • get(USER_UUID): 데이터는 Key-Value 형태로 저장되므로, 필요한 값을 Key를 통해 얻습니다.

마무리

이번 프로젝트에서 JWT를 활용해 인증을 구현하며, 세션 기반 인증의 단점을 해결하고 서버 부하를 줄일 수 있었습니다. 이를 통해 사용자 경험을 향상시키는 동시에 안정적인 서버 운영을 가능하게 했습니다.

위에서 소개한 코드와 로직은 Spring Boot 환경에서 쉽게 적용할 수 있으니, 유사한 문제를 겪는 분들에게 도움이 되었으면 합니다.

 

혹시라도 틀린 내용이 있다면 댓글로 알려주시면 감사하겠습니다!!

728x90
반응형

+ Recent posts