배경
- 인증을 위해 JWT를 사용하고 있다. JWT의 Claim을 통해 사용자 정보를 가져온다.
- User 엔티티는 AbstractJpaEntity로부터 상속받는다. id를 이용해 바로 객체를 생성할 수 없다.
- Java 17, Spring 3.0으로 최신 개발환경이다.
기존 코드(Spring Security Filter 일부분)
Long id = decodedJWT.getClaim("id").asLong();
String role = decodedJWT.getClaim("role").asString();
MemberRole roleEnum = MemberRole.valueOf(role);
Member member = Member.builder().id(id).role(roleEnum).build();
CustomUserDetails myUserDetails = new CustomUserDetails(member);
Authentication authentication =
new UsernamePasswordAuthenticationToken(
myUserDetails,
null,
myUserDetails.getAuthorities()
);
...
상황
- JWT Claim을 통해 사용자 정보를 가져와 CustomUserDetails를 만든다.
- User 엔티티가 AbstractJpaEntity로 상속받도록 수정되면서, id를 직접 설정하여 User를 만들 수 없으므로 필터 내에서 UserRepository의 User를 조회해야한다.
문제
- JwtAuthenticationFilter에 UserRepository 주입이 제대로 되지 않는다. (다른 방법을 찾아봐야한다.)
해결방법
- JwtAuthenticationFilter 내에 UserFindService(의존성 주입을 위한 서비스)를 주입한다.
1. UserFindService 만들기
import java.util.Collections;
@Service
public class UserFindService {
private final UserRepository userRepository;
@Autowired // 의존성 주입
public UserFindService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 작업 수행 (유저 찾기)
public User getUserById(Long id) throws Exception {
return userRepository.findById(id)
.orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND,
Collections.singletonMap("User", "User not found")));
}
}
2. JwtAuthenticationFilter에서 UserFindService 사용하기
@Slf4j
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
private final UserFindService userUtilityService; // 의존성 주입 서비스
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, UserFindService userUtilityService) {
super(authenticationManager);
this.userUtilityService = userUtilityService;
}
...
if (jwt != null && !isNonProtectedUrl(request)) { // 토큰이 있고 보호된 URL일 경우 토큰 검증
DecodedJWT decodedJWT = JWTProvider.verify(jwt);
Long id = decodedJWT.getClaim("id").asLong();
// 서비스 사용하기
User user = userUtilityService.getUserById(id);
CustomUserDetails myUserDetails = new CustomUserDetails(user);
Authentication authentication =
new UsernamePasswordAuthenticationToken(
myUserDetails,
null,
myUserDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
필터의 생성자를 통해 중간 서비스를 주입받아 필요한 의존성을 전달받는다.
그렇다면 왜 필터 내에서는 userRepository 의존성 주입이 되지 않을까?
Spring Security에서 의존성 주입이 작동하지 않는 이유
=> 필터의 생명주기 때문이다.
Spring Security의 필터들은 Spring Application Context의 일반적인 빈(Bean) 생명주기를 따르지 않는다.
그대신 보안 설정에서 명시적으로 생성, 구성된다.
그렇기 때문에 의존성 주입을 위해서 UserFindService(유틸리티) 서비스를 만들면, 그러한 서비스들을 애플리케이션 컨텍스트에 의해 관리되므로 빈을 쉽게 주입할 수 있다.
그런 다음 필터에서 이러한 서비스를 사용하면 원하던 작업을 수행할 수 있다.
또한 이렇게 의존성을 분리시킴으로써 Spring 구성요소 간의 결합도도 낮출 수 있다.
참고
- ApplicationContext를 이용해 필요한 빈을 동적으로 조회하는 방법도 있지만, 필터가 ApplicationContext가 완전히 초기화되기 전에 생성되었으므로 (유추) 아쉽게도 내 코드에서는 작동하지 않았다
'문제&해결' 카테고리의 다른 글
도커 컨테이너의 ip 주소는 왜 127.0.0.1(localhost)가 아닐까? (0) | 2024.03.09 |
---|---|
로컬, docker 컨테이너 포트 충돌 : 포트 관리 중요성 (mysql) (0) | 2024.03.09 |
웹소켓 취약점 해결 : 인증 도입 (STOMP, Jwt, Spring Security) (3) | 2024.02.20 |
도커 volume 설정 + 환경 변수를 사용하여 H2 DB Url을 동적으로 설정하기 (로컬 / Docker 환경) (0) | 2024.01.30 |
yml 파일이 깃허브에 올라갔을때 커밋에서 삭제하는 방법 (0) | 2023.09.29 |