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() 수행

 

duplicateKeyException

Environment
Language: Java
DB: MySQL

 

오류

Caused by: java.lang.IllegalStateException: Duplicate key 400 BAD_REQUEST (attempted merging values DUPLICATED_EMAIL and NOT_EQUAL_PASSWORD)
	at java.base/java.util.stream.Collectors.duplicateKeyException(Collectors.java:135)
	at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:182)

 

 

원인

DUPLICATED_EMAIL(HttpStatus.BAD_REQUEST, "이미 존재하는 이메일 입니다."),
NOT_EQUAL_PASSWORD(HttpStatus.BAD_REQUEST,"입력한 비밀번호가 상이합니다."),

// HttpStatus -> ErrorCode 조회
private static final Map<HttpStatus, ErrorCode> BY_HTTPSTATUS =
        Stream.of(values()).collect(Collectors.toMap(ErrorCode::getHttpStatus, e -> e));

public static Optional<ErrorCode> valueOfHttpStatus(HttpStatus httpStatus){
    return Optional.ofNullable(BY_HTTPSTATUS.get(httpStatus));
}

 

  • toMap()
    • If the mapped keys contain duplicates (according to Object.equals(Object)), an IllegalStateException is thrown when the collection operation is performed. If the mapped keys might have duplicates, use toMap(Function, Function, BinaryOperator) instead.
    • HttpStatus로부터 Enum을 조회하려고 할 때, Map을 활용하는데 이 때 key 값(= HttpStatus)이 중복될 경우, duplicate관련 IllegalStateException이 발생할 수 있음

 

해결

// HttpStatus -> ErrorCode 조회
private static final Map<HttpStatus, ErrorCode> BY_HTTPSTATUS =
        Stream.of(values()).collect(Collectors.toMap(ErrorCode::getHttpStatus, e -> e, ((e1, e2) -> e1)));
        // Stream.of(values()).collect(Collectors.toMap(ErrorCode::getHttpStatus, e -> e, ((e1, e2) -> e2)));

public static Optional<ErrorCode> valueOfHttpStatus(HttpStatus httpStatus){
    return Optional.ofNullable(BY_HTTPSTATUS.get(httpStatus));
}

 

  • toMap(Function, Function, BinaryOperator)
    • BinaryOperator에서 먼저 put할 데이터를 사용할지 나중에 put한 데이터를 사용할지 추가

 

📚 참고 자료

 

[Error/Exception] java.lang.IllegalStateException: Duplicate key ‘key로 저장하려는 값’ (attempted merging values ~~)

에러 발생java.lang.IllegalStateException: **Duplicate key 'key로 저장하려는 값'** (attempted merging values ~~) 오늘도 만난 에러,, 뜯어봐야 알겠지만 대충 봐도 key 값이 중복되어 발생한 에러다.  에러 원인에

hoehen-flug.tistory.com

 

EmptyResultDataAccessException

Environment
Language: Java
DB: MySQL

 

오류

Caused by: jakarta.persistence.NoResultException: No result found for query [select u from User u where u.email = :email]
	at org.hibernate.query.spi.AbstractSelectionQuery.getSingleResult(AbstractSelectionQuery.java:476)
	at com.msgs.user.repository.UserRepository.findByEmail(UserRepository.java:19)

 

 

원인

public Optional<User> findByEmail(String email) {
    User user = em.createQuery("select u from User u where u.email = :email", User.class)
            .setParameter("email", email)
            .getSingleResult();
    return Optional.ofNullable(user);
}

 

  • getSingleResult()
  1. 결과가 없을 경우, NoResultException 예외 발생
  2. 결과가 2개 이상일 경우, NonUniqueResultException 예외 발생

 

해결

public Optional<User> findByEmail(String email) {
    List<User> users = em.createQuery("select u from User u where u.email = :email", User.class)
            .setParameter("email", email)
            .getResultList();
    if (users.isEmpty()) {
        return Optional.empty();
    } else {
        return Optional.of(users.get(0));
    }
}

 

  • getSingleResult() → getResultList()

 

JdbcTypeRecommendationException

Environment

  • Language: Java
  • DB: H2 Database
  • IDE: IntelliJ

 

Problem

@Entity를 선언하고 table을 create할 때 발생

Caused by: org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException: Could not determine recommended JdbcType for Java type 'jpabook.jpashop.domain.Delivery'

 

Cause of Problem

Hibernate가 엔티티의 필드에 대한 JDBC 타입을 결정하지 못할 때 발생

엔티티 필드와 데이터베이스 컬럼 간의 매핑이 충분히 명시되지 않았거나 잘못되었을 때 발생

관계 설정을 하지 않는다면, Hibernate는 해당 관계를 어떻게 매핑해야 하는지 알 수 없게 되어, Java의 데이터 타입을 SQL 데이터베이스의 데이터 타입에 매핑하지 못하고 JdbcTypeRecommendationException이 발생

 

Solution

  • Delivery Entity가 사용된 Entity에서 @OneToMany 등의 관계 설정이 제대로 이뤄졌는지 확인
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter @Setter
public class Member {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();

    private Delivery delivery;

}

 

📚 참고 자료