강의

멘토링

로드맵

인프런 커뮤니티 질문&답변

Link님의 프로필 이미지
Link

작성한 질문수

코드로 배우는 React 19 with 스프링부트 API서버

JWTUtil 에 심각한 버그 존재?

해결된 질문

작성

·

82

·

수정됨

0

안녕하세요.

최초 10분이 지난 후 refreshToken 정보를 이용하여, new accessToken 을 발급받는 것을 적용한 이후에는 오랜 시간이 지나도 다시 new accessToken 발급을 하지 않는 현상이 있더군요.

의문을 가지다가 Redis 에 연동하고, ttl JWT:ACCESS:user8@aaa.com 로 accessToken의 남은 시간을 체크해보니 문제가 있다는 걸 확인했습니다.

chatGPT 로 점검해서 아래와 같은 코드로 수정한 이후 정상 동작하는 거 같습니다.

public static String generateToken(Map<String, Object> valueMap, int min) {

    SecretKey key = null;

    try {
        key = Keys.hmacShaKeyFor(JWTUtil.key.getBytes("UTF-8"));

    } catch (Exception e) {
        throw new RuntimeException(e.getMessage());
    }

    // 1. claims 복사 및 exp/iat 제거
    Map<String, Object> claims = new HashMap<>(valueMap);
    claims.remove("exp");
    claims.remove("iat");

    // 2. 발급시간/만료시간 생성
    Date now = Date.from(ZonedDateTime.now().toInstant());
    Date exp = Date.from(ZonedDateTime.now().plusMinutes(min).toInstant());

    // 3. JWT 생성
    return Jwts.builder().header()
            .add("typ", "JWT")
            .add("alg", "HS256")
            .and()
            .issuedAt(now)
            .expiration(exp)
            .claims(claims)
            .signWith(key)
            .compact();

}

답변 3

0

구멍가게코딩단님의 프로필 이미지
구멍가게코딩단
지식공유자

말씀하신 내용 중에 좀 이상하다고 생각되었던 부분이

 

-- 최초에는 accessToken 10분 제대로 할당됨.
-- refreshToken 으로 new accessToken 이 1일 단위로 할당되는 현상이었음.

 

라고 하셔서 그럼 처음 로그인했을때 만들어진 엑세스 토큰도 1일 단위가 되어야 하는데.. 라고 생각했습니다.

 

그래서 좀 더 코드를 살펴보니 예제 코드에 있는 부분에서 .claims( )위치가 다르네요..

 

강의에 파일로 제공된 코드는.. 마지막에 claims( )를 했습니다.

.issuedAt(Date.from(ZonedDateTime.now().toInstant()))
.expiration((Date.from(ZonedDateTime.now()
        .plusMinutes(min).toInstant()))).claims(valueMap)

 

이렇게 되면 valueMap의 iat와 exp가 영향을 주게 되는 것을 확인했습니다(새로 발행된 값 대신에 덮어쓰는 현상이 ㅠㅠ)

 

이 문제 때문에 RefreshToken의 exp값이 새로 발행된 Access Token의 exp값이 되었던 것 같습니다.

 

 

claims( )를 한 후에 issueAt( )과 expiration( )이 실행되어야 합니다.

.claims(valueMap)
.issuedAt(Date.from(ZonedDateTime.now().toInstant()))
.expiration((Date.from(ZonedDateTime.now()
        .plusMinutes(min).toInstant())))

 

 


꼼꼼하게 살펴보지 못한 제 불찰입니다. 죄송합니다.

 

0

구멍가게코딩단님의 프로필 이미지
구멍가게코딩단
지식공유자

현재 강의 섹션의 마지막에 있는 예제코드를 다시 확인해 보았습니다.

 

Access Token은 10분

Refresh Token은 하루 뒤로 정상적으로 생성되고 있는 것을 확인하였습니다(마찬가지로 정상적으로 갱신되는 것도 확인했습니다.).

 

리액트를 갱신하면서 부트 쪽은 라이브러리의 버전들만 업데이트가 시키고 큰 내용에는 차이가 없긴 합니다만.. 혹시 그 문제일지도 모르겠습니다.

 

질문 올려주신 코드는 JJWT 0.11.X 버전에 맞는 코드입니다.

 

JJWT0.12.x 버전에서는 (현재 섹션 마지막의 코드 버전) 약간의 차이가 있긴 합니다.

 

    return Jwts.builder()
        .header() // 헤더 DSL 시작
            .add("typ", "JWT")
            .add("alg", "HS256") // 보통 생략 가능
        .and() // 헤더 DSL 종료
        .issuedAt(Date.from(ZonedDateTime.now().toInstant())) // 발급 시간
        .expiration(Date.from(ZonedDateTime.now().plusMinutes(min).toInstant())) // 만료 시간
        .claims(valueMap) // 클레임 설정
        .signWith(key) // 서명
        .build() // Jwt 객체 생성
        .compact(); // 문자열로 변환

 

 

Link님의 프로필 이미지
Link
질문자

제가 겪는 현상은 최초에는 accessToken 10분 제대로 할당됨.
이후 refreshToken 으로 new accessToken 이 1일 단위로 할당되는 현상이었음.

코드를 위와 같이 수정하고 나서 Redis 에서 10분 뒤에 accessToken 정보는 삭제되고

refreshToken 을 이용하여 new accessToken 정상 할당 확인.

중복 로그인 체크 기능 구현 위해서 Redis 연동 테스트를 하고 있는 중입니다.

제가 사용하는 build.gradle dependencies 입니다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
    annotationProcessor 'org.projectlombok:lombok'

    implementation 'org.modelmapper:modelmapper:3.2.2'
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'

    implementation 'org.owasp.encoder:encoder:1.3.1'
    implementation 'commons-io:commons-io:2.11.0'
    implementation 'me.paulschwarz:spring-dotenv:3.0.0'

    //QueryDSL 추가
    implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
    annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"

    implementation 'net.coobird:thumbnailator:0.4.20'

    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'com.google.code.gson:gson:2.12.1'

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

    implementation 'org.springframework.boot:spring-boot-starter-validation'

    // P6Spy
//    implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0'
    implementation 'org.hibernate.orm:hibernate-core:6.6.12.Final' // 최신 버전

    // Redis
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'

    implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5'

    //test 롬복 사용
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
    testImplementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

}

0

안녕하세요, 인프런 AI 인턴입니다. Link님.

JWT 토큰의 발급 및 만료 관련 문제를 잘 해결하신 것 같습니다. Redis에 연동하여 AccessToken의 남은 시간을 체크하는 과정에서 버그를 발견하고 수정 후 정상 동작하게 된 점 축하드립니다. 이번 기회를 통해 JWT 내부의 토큰 발급 및 갱신 프로세스를 어떻게 개선하셨는지에 대해 공유해주셔서 감사합니다.

원인 및 해결 과정

  • 토큰 갱신 문제: 기존의 JWT 발급 시 ‘exp’(만료) 및 ‘iat’(발급) 클레임의 관리 오류가 Redis TTL과 맞지 않는 현상으로 나타났습니다. 이로 인해 새로운 AccessToken 발급이 필요할 때에도 발급되지 않는 문제를 경험하셨을 것입니다.

  • 해결 방법: ‘exp’와 ‘iat’ 클레임을 수동으로 제거한 후 토큰 발급 시간을 명시적으로 추가하여, 새로운 토큰에 맞게 발급 및 갱신 로직을 수정함으로써 Redis와의 TTL 문제도 해결하였습니다.

이렇게 수정을 진행하면서 JWT를 처리하는 데 있어 클레임 관리의 중요성을 깨달을 수 있습니다.

유사한 문제 사례

아래의 경우와 같이 관련 토큰 갱신 문제 사례들이 도움될 수 있습니다. 이미 잘 해결하셨지만, 추가 참고로 링크를 안내합니다:

이처럼 JWT와 스프링 시큐리티 연동 과정에서는 클레임 관리와 시간이 중요한 역할을 합니다. 추가적인 질문이 있으시면 언제든지 질문해 주세요. 곧 지식공유자께서도 직접적인 도움을 주실 것입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

Link님의 프로필 이미지
Link

작성한 질문수

질문하기