io.jsonwebtoken.ExpiredJwtException

Environment
Language: Java 17
DB: MySQL, Redis

 

오류

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws IOException, ServletException {
    // 1. Request Header에서 JWT 토큰 추출
    String accessToken = SecurityUtils.resolveToken(request);
 
    // 2. 토큰 유효성 검사
    // 토큰이 유효할 경우, 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장
    if (accessToken != null && jwtTokenProvider.isValidToken(accessToken)) {
        Authentication authentication = jwtTokenProvider.getAuthentication(accessToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
 
    filterChain.doFilter(request, response);
    // 현재 필터가 요청을 처리한 후, 필터 체인의 다음 필터로 요청과 응답을 전달
}

accessToken이 예전 accessToken값을 들고와서, ExpiredJwtException 발생

 

 

원인

1
localhost:8080/api/v2/users/re-issue

Postman 요청 시, Authorization에서 Token이 입력되어있는지 확인

 

 

해결

Postman 요청 시, Authorization에서 Token 삭제

Body에 Argument 전달

 

JWT Login 시, accessToken이 null

Environment
Language: Java
DB: MySQL

 

오류

private Claims parseClaims(ServletRequest request) {
    String accessToken = SecurityUtils.resolveToken((HttpServletRequest) request);
    if(accessToken == null)
        return null;

    Key secretKey = Keys.hmacShaKeyFor(JWT_SECRET.getBytes(StandardCharsets.UTF_8));

    return Jwts.parserBuilder()
            .setSigningKey(secretKey)
            .build()
            .parseClaimsJws(accessToken)
            .getBody();
}

Header Type, Request Body의 데이터가 정확함에도 accessToken이 null

 

 

원인

@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws IOException, ServletException {
    // 1. Request Header에서 JWT 토큰 추출
    Authentication authentication = jwtTokenProvider.getAuthentication(request);

    // 2. 토큰 유효성 검사
    // 토큰이 유효할 경우, 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장
    if (authentication != null && jwtTokenProvider.isValidToken(request)) {
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    filterChain.doFilter(request, response);
    // 현재 필터가 요청을 처리한 후, 필터 체인의 다음 필터로 요청과 응답을 전달
}

 

SpringSecurity와 JWT가 동작하는 과정

  1. 애플리케이션 시작 → Spring Security의 초기화 및 설정 과정(Filter 등)
  2. API 호출 → JwtAuthenticationFilter 요청 처리
    필터 통과: 요청을 다음 필터로 전달
    필터 통과 X: 오류 반환
  3. 필터 체인을 모두 통과한 요청은 Controller로 전달
  4. Controller → UserService 호출
  5. UserService에서 AuthenticationManager은 UserDetailsService 호출
  6. AuthenticationManagerBuilder 동작
  7. AuthenticationManagerBuilder에서 사용자가 제공한 정보(이메일과 비밀번호)를 확인
    * 사용자가 입력한 이메일과 비밀번호를 담은 인증 토큰 생성
  8. AuthenticationManagerBuilder에서 authentificate() 호출하여 인증 시도
    내부적으로 CustomUserDetailsService의 loadUserByUsername() 호출
    *주어진 이메일로 데이터베이스에서 사용자를 찾아서 그 정보를 UserDetails 객체로 반환
  9. AuthenticationManagerBuilder에서 6과 7의 객체 비교
    * 인증 성공: Authentication 객체는 SecurityContext에 저장, 이후의 요청에서 사용자 정보를 참조할 수 있음
    * 인증 실패: BadCredentialsException 발생
  10. UserService에서 JwtTokenProvider의 generateToken() 호출
 
JwtAuthenticationFilter 처리 시, 모든 요청에 대해 getAuthentification() 동작

→ 로그인 시에는 token이 없어 Header에 token을 넣고 요청할 수 없음

→ accessToken이 null

 

⭐ Controller 처리 전에 오류가 발생, Controller에서 디버그해도 넘어오지 않음⭐

 
 

해결

@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws IOException, ServletException {
    String accessToken = SecurityUtils.resolveToken(request);

    if (accessToken != null && jwtTokenProvider.isValidToken(request)) {
        Authentication authentication = jwtTokenProvider.getAuthentication(request);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    filterChain.doFilter(request, response);
}

getAuthentification()이 아닌 토큰을 추출하는 메서드(resolveToken())를 수행

→ null이 아닐 때 getAuthentification() 수행

 

👉 기본 환경

- Language: Java

- DB: MySQL

- IDE: IntelliJ

 

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.4'
    id 'io.spring.dependency-management' version '1.1.3'
}
 
group = 'com.project'
version = '0.0.1-SNAPSHOT'
 
java {
    sourceCompatibility = '17'
}
 
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
 
repositories {
    mavenCentral()
}
 
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
 
    // .jsp
    implementation 'javax.servlet:jstl'
    implementation "org.apache.tomcat.embed:tomcat-embed-jasper"
 
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.mysql:mysql-connector-j'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
 
tasks.named('test') {
    useJUnitPlatform()
}
 
 

 

 

🖨️오류

1
2
3
4
5
6
7
8
9
Execution failed for task ':compileJava'.
> Could not resolve all files for configuration ':compileClasspath'.
   > Could not find javax.servlet:jstl:.
     Required by:
         project :
 
Possible solution:
 - Declare repository providing the artifact, see the documentation at https://docs.gradle.org/current/userguide/declaring_repositories.html
 
 

build.gradle에서 jsp 사용을 위한 jstl이 적용되지 않음

 

 

📡 원인

Springboot 3.대부터는 jstl implementation 방법이 변경됨

 

 

📰 해결 방법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.4'
    id 'io.spring.dependency-management' version '1.1.3'
}
 
group = 'com.project'
version = '0.0.1-SNAPSHOT'
 
java {
    sourceCompatibility = '17'
}
 
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
 
repositories {
    mavenCentral()
}
 
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
 
    // .jsp
    // implementation 'javax.servlet:jstl' //스프링부트 3.0 미만
    implementation 'jakarta.servlet:jakarta.servlet-api' //스프링부트 3.0 이상
    implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api' //스프링부트 3.0 이상
    implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl' //스프링부트 3.0 이상
    implementation "org.apache.tomcat.embed:tomcat-embed-jasper"
 
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.mysql:mysql-connector-j'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
 
tasks.named('test') {
    useJUnitPlatform()
}
 
 

Springboot 3.대에서 사용할 수 있게 jstl implementation 변경

 

 

⭐ GPT도 좋지만, Google 검색을 다양하게 시도해보자 ⭐

 

 

 

📚 참고 자료

 

(spring 3.0 version) jstl 이 재대로 적용되지 않을때 - 인프런 | 고민있어요

혹시나 spring 3.0 버전을 사용하고 계신분이라면implementation 'javax.servlet:jstl'대신에implementation group: 'org.glassfish.web', name: 'jakarta.servlet.jsp.jstl', version...

www.inflearn.com

 

👉 기본 환경

- Language: Java

- DB: MySQL

- IDE: IntelliJ

 

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
plugins {
    id 'java'
}
 
group = 'kr.co.project1'
version = '1.0'
 
repositories {
    mavenCentral()
}
 
dependencies {
    testImplementation platform('org.junit:junit-bom:5.9.1')
    testImplementation 'org.junit.jupiter:junit-jupiter'
 
    compile 'javax.servlet:servlet-api:2.5'
    compile 'org.springframework:spring-webmvc:4.3.18.RELEASE'
}
 
test {
    useJUnitPlatform()
}
 
 

 

 

🖨️오류

 

 

 

📡 원인

gradle.build 파일 내 오류

Could not find method compile() for arguments [javax.servlet:servlet-pi:2.5] on root project 'one_step' of type org.gradle.api.Project.

: org.gradle.api.Project 유형의 루트 프로젝트 'one_step'에서 [javax.servlet:servlet-pi:2.5] 인자에 대한 compile() 메서드를 찾을 수 없음

 

+ Gradle 7.0 버전부터는 compile 메서드 대신 implementation과 compileOnly 등의 의존성 구성 메서드 사용

 

 

📰 해결 방법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
plugins {
    id 'java'
}
 
group = 'kr.co.project1'
version = '1.0'
 
repositories {
    mavenCentral()
}
 
dependencies {
    testImplementation platform('org.junit:junit-bom:5.9.1')
    testImplementation 'org.junit.jupiter:junit-jupiter'
 
    implementation 'javax.servlet:servlet-api:2.5'
    implementation 'org.springframework:spring-webmvc:4.3.18.RELEASE'
 
}
 
test {
    useJUnitPlatform()
}
 
 

implementation으로 의존성 추가

 

👉 기본 환경

- Language: Java

- DB: MySQL

- IDE: IntelliJ

 

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
 
@Configuration
public class QuerydslConfiguration {
 
    @PersistenceContext
    private EntityManager entityManager;
 
    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
 
}
 
 
 

 

 

🖨️오류

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:98)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
    at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)
    at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
    at org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:42)
    at org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:80)
    at org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:72)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
    at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:110)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:90)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:85)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'jpaQueryFactory' defined in com.example.demo.DemoApplication: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=demoApplication; factoryMethodName=jpaQueryFactory; initMethodName=null; destroyMethodName=(inferred); defined in com.example.demo.DemoApplication] for bean 'jpaQueryFactory': There is already [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=querydslConfiguration; factoryMethodName=jpaQueryFactory; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/example/demo/config/QuerydslConfiguration.class]] bound.
    at app//org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:1006)
    at app//org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:295)
 
 
 

 

 

📡 원인

오류 구문: "Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException"

    - 두 개의 동일한 이름의 빈(Bean)이 스프링 컨텍스트 내에 중복으로 등록되었을 때 발생

 

👓 자세히

 Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException:

Invalid bean definition with name 'jpaQueryFactory' defined in com.example.demo.DemoApplication:

Cannot register bean definition for bean 'jpaQueryFactory':

There is already  defined in class path resource [com/example/demo/config/QuerydslConfiguration.class] bound.

    - DemoApplication 내에 jpaQueryFactory 빈을 등록하려고 했으나, QuerydslConfiguration에서 이미 동일한 이름의 빈을 등록

 

 

📰 해결 방법

jpaQueryFactory 빈 등록을 DemoApplication 또는 QuerydslConfiguration에서 한 번만 등록