문제 해결 과정 (Problem Solving Journey)/버그 픽스 스토리

JWT 토큰 발급 로그인에서 순환 참조 문제 해결

코린이ch 2024. 10. 12. 10:33

# 스프링 부트에서 순환 참조 문제 해결과 JWT 인증 시스템 구축기

스프링 부트로 프로젝트를 진행하면서 순환 참조(circular reference) 오류와 로그인 실패 문제를 겪었습니다. 이 글에서는 문제의 원인을 파악하고 해결해 나간 과정을 공유하고자 합니다. 특히, 스프링 시큐리티와 JWT를 활용한 인증 시스템을 구축하는 과정에서의 주요 개념과 교훈들을 다룰 예정입니다.

1. 순환 참조 오류와 의존성 주입 방식 개선

- 순환 참조란?

순환 참조는 두 개 이상의 빈(bean)이 서로를 참조하는 경우에 발생하는 문제입니다. 스프링 부트에서는 이런 상황이 발생하면 애플리케이션 실행 시점에 오류가 발생합니다. 저도 프로젝트에서 비슷한 문제가 발생했는데, 이는 필드 주입 방식으로 인해 의존성이 꼬여 발생한 것이었습니다.

> 문제 해결 과정

- 의존성 주입 방식 변경: 처음에는 필드 주입(field injection)을 사용하고 있었습니다. 이 방식을 생성자 주입(constructor injection)으로 변경하니 순환 참조 오류가 해결되었습니다. 생성자 주입 방식은 컴파일 시점에 주입할 객체를 설정해주기 때문에, 순환 참조를 피하고 더 안전한 코드 작성이 가능합니다.
- PasswordEncoder 빈 처리: `SecurityConfig`에서 `PasswordEncoder` 빈을 정의하고, 이를 정적 메소드로 선언하여 모든 클래스에서 안전하게 접근할 수 있도록 설정했습니다.

교훈

- **생성자 주입을 사용하자**: 스프링 프로젝트에서 의존성 주입 시 가능하면 생성자 주입을 사용하는 것이 좋습니다. 이렇게 하면 순환 참조 문제를 피할 수 있고, 테스트 코드 작성 시에도 유연성이 더 높아집니다.




2. 사용자 인증 구현: 스프링 시큐리티와 JWT 통합

- 스프링 시큐리티와 JWT란?

스프링 시큐리티(Spring Security)는 스프링 애플리케이션에서 인증과 인가를 처리하기 위한 강력한 보안 프레임워크입니다. JWT(JSON Web Token)는 인증 정보를 JSON 형태로 담아 서버와 클라이언트 간에 안전하게 전달하기 위한 토큰입니다. 이를 통해 서버는 상태를 유지하지 않으면서도 사용자를 인증할 수 있습니다.

- JWT 토큰 생성과 검증

저는 프로젝트에서 `JwtTokenProvider` 클래스를 구현해 JWT 토큰을 생성하고 검증하는 로직을 작성했습니다. 이 클래스는 다음의 주요 기능을 수행합니다:

토큰 생성: 사용자가 로그인에 성공하면 해당 사용자 정보를 기반으로 JWT 토큰을 생성합니다.
토큰 검증: 이후 요청이 들어올 때마다 토큰의 유효성을 검증하여 사용자 정보를 확인합니다.

### SecurityConfig 설정

JWT를 적용하려면 `SecurityConfig` 클래스에 JWT 필터를 추가해야 합니다. 저는 `UsernamePasswordAuthenticationFilter` 이전에 `JwtFilter`를 추가해 모든 요청에 대해 JWT 인증을 수행하도록 설정했습니다.

SecurityConfigure예시


```java
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .authorizeRequests()
        .antMatchers("/auth/**").permitAll()
        .anyRequest().authenticated()
        .and()
        .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
```

CustomUserDetailsService 수정

기존에는 이메일로 사용자를 조회했지만, 프로젝트 요구사항에 따라 `membername`으로 사용자를 조회하도록 변경했습니다. 이를 통해 사용자 조회 로직의 일관성을 유지할 수 있었습니다. (바보처럼 이메일과 혼용했었음)




3. JWT 토큰 발급과 유효기간 관리

JWT 토큰 유효기간 설정

JWT 토큰의 유효기간은 24시간으로 설정했습니다. 이는 사용자 경험과 보안성을 동시에 고려한 결정이었습니다. 짧은 유효기간은 보안을 강화하지만, 사용자가 자주 로그인을 해야 하는 불편함이 생길 수 있습니다. 반면, 긴 유효기간은 보안에 취약할 수 있지만, 사용자 편의성은 높아집니다. 저는 두 가지 사이에서 적절한 균형을 찾기 위해 24시간을 선택했습니다. 추후 수정 계획을 하고 있긴 합니다만..

토큰 갱신 로직

현재 구현에서는 토큰 갱신 기능을 추가하지 않았지만, 이후에는 만료된 토큰을 갱신하는 로직도 고려하고 있습니다. 이를 통해 사용자가 로그아웃되지 않고 서비스를 이용할 수 있도록 개선할 예정입니다.




4. 최종 결과

이 과정을 통해 순환 참조 문제와 인증 오류를 해결하고, JWT 기반의 안전한 인증 시스템을 구축할 수 있었습니다. 이제 사용자 로그인 시 성공적으로 JWT 토큰이 발급되고, 이후 모든 요청에서 이 토큰을 사용해 인증을 수행할 수 있습니다.

마무리하며
프로젝트를 진행하면서 발생한 문제들을 해결하는 과정은 때로는 고통스럽지만, 그만큼 많은 것을 배우게 해줍니다. 특히, 스프링 부트에서의 의존성 관리와 JWT 인증 시스템을 구현하며 배운 것들은 앞으로의 개발에 큰 도움이 될 것입니다. 이 글이 스프링 부트와 JWT 인증 시스템을 처음 접하는 분들께 조금이나마 도움이 되기를 바랍니다.