트러블슈팅 기록
아래는 프로젝트 개발 중 발생했던 문제들과 이를 해결한 방법을 정리한 트러블슈팅 문서입니다.
1. @Auth와 ArgumentResolver의 역할
문제:
기존 코드에서 ArgumentResolver를 사용하여 JWT 필터에서 인증된 정보를 AuthUser 객체로 컨트롤러에 전달.
Spring Security를 적용하면서, @AuthenticationPrincipal을 통해 SecurityContext에서 유저 정보를 직접 가져올 수 있음.
ArgumentResolver가 더 이상 필요하지 않을 것 같지만, 정확한 판단이 어려웠음.
해결:
Spring Security의 @AuthenticationPrincipal로 유저 정보를 가져오는 방식을 채택.
기존 ArgumentResolver 코드를 삭제하여 불필요한 복잡도를 제거.
결과:
컨트롤러에서 @AuthenticationPrincipal을 통해 간결하게 유저 정보 접근 가능.
불필요한 코드가 줄어들어 유지보수성이 향상됨.
2. 403 Forbidden 에러 발생
문제:
/signup 엔드포인트에서 403 Forbidden 에러가 발생.
원인은 Security 설정에서 /signup 경로가 인증이 필요하도록 설정되어 있었음.
해결:
SecurityConfig의 SecurityFilterChain 설정을 수정:
.authorizeHttpRequests(auth -> auth
.requestMatchers("/signup").permitAll()
.anyRequest().authenticated()
)
/signup 경로를 인증 없이 접근할 수 있도록 permitAll()로 설정.
결과:
/signup 엔드포인트에서 더 이상 인증 오류가 발생하지 않고, 회원가입 요청이 정상적으로 처리됨.
3. 데이터베이스 오류: "Connection is read-only"
문제:
회원가입 요청 시 INTERNAL_SERVER_ERROR와 함께 다음 오류 발생:
could not execute statement [Connection is read-only. Queries leading to data modification are not allowed]
원인은 Hibernate가 사용하는 데이터베이스 연결이 "read-only" 상태였음.
해결:
트랜잭션 관리 확인:
@Transactional 어노테이션이 클래스 또는 메소드에 적용되어 있는지 확인.
데이터베이스 설정 확인:
데이터베이스 연결 설정에서 readOnly 옵션이 활성화되어 있는지 확인하고 비활성화.
application.properties 또는 application.yml 파일에서 확인:
spring.datasource.hikari.read-only=false
문제 해결 후 정상 작동 확인.
결과:
데이터베이스 쓰기 작업이 정상적으로 수행되고, 회원가입 기능이 작동함.
4. JWT 토큰 만료 문제
문제:
만료된 JWT 토큰으로 요청 시, 다음과 같은 오류 로그가 출력:
Invalid JWT token: JWT expired at ...
만료된 토큰을 사용할 수 없으므로, 유저가 계속 요청 실패.
해결:
만료된 토큰의 예외 처리 추가:
JwtUtil에서 만료된 토큰에 대한 사용자 친화적인 응답을 반환하도록 설정.
} catch (ExpiredJwtException e) {
log.error("Expired JWT token", e);
throw new AuthException("만료된 토큰입니다.", HttpStatus.UNAUTHORIZED);
}
토큰 갱신 로직 추가:
클라이언트가 만료된 토큰을 감지하여 새 토큰을 요청하도록 구현.
결과:
만료된 토큰을 사용할 때 적절한 응답 반환.
클라이언트가 토큰 갱신 작업을 처리하여 인증 유지 가능.
5. Spring Security 6.1 버전에서 메서드가 Deprecated
문제:
Spring Security 6.1 버전 이후, HttpSecurity의 여러 메서드가 Deprecated 처리됨:
csrf(), sessionManagement(), authorizeHttpRequests() 등이 해당.
해결:
Deprecated 메서드 교체:
최신 방식으로 Security 설정 변경:
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/signup").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
Deprecated 메서드에 대한 공식 문서 확인 후 적용.
결과:
Spring Security 설정이 최신 방식으로 업데이트되었으며, 코드 유지보수성이 개선됨.
6. JWT 필터에서 인증 정보를 SecurityContext에 저장
문제:
JWT 필터에서 인증된 유저 정보를 SecurityContext에 저장하지 않아 인증 상태가 유지되지 않음.
해결:
JWT 필터 수정:
인증 성공 시 SecurityContext에 유저 정보를 저장하도록 변경:
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
username, null, jwtUtil.getRolesFromToken(token).getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authentication);
유저 권한 설정 확인:
Spring Security가 권한을 올바르게 관리할 수 있도록 설정.
결과:
JWT 인증이 성공적으로 적용되었으며, 인증 상태가 요청 전체에 걸쳐 유지됨.
7. CustomPasswordEncoder와 Spring PasswordEncoder 간 충돌
문제:
기존에 커스텀으로 만든 CustomPasswordEncoder와 Spring의 PasswordEncoder가 충돌하여 암호화 로직에서 오류 발생.
해결:
CustomPasswordEncoder 삭제:
Spring Security에서 제공하는 BCryptPasswordEncoder로 통합:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
코드에서 CustomPasswordEncoder를 사용하는 모든 부분을 PasswordEncoder로 수정.
결과:
암호화 및 검증 로직이 Spring Security 표준에 맞게 수정되었고, 충돌이 해결됨.