발생 상황
- 관리자 권한 검증을 위한 어노테이션과 ArgumentResolver를 구현했음에도 예외가 발생하였습니다.
org.springframework.dao.InvalidDataAccessApiUsageException: The given id must not be null
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:371) ~[spring-orm-6.0.11.jar:6.0.11]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:234) ~[spring-orm-6.0.11.jar:6.0.11]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550) ~[spring-orm-6.0.11.jar:6.0.11]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-6.0.11.jar:6.0.11]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) ~[spring-tx-6.0.11.jar:6.0.11]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152) ~[spring-tx-6.0.11.jar:6.0.11]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:164) ~[spring-data-jpa-3.1.3.jar:3.1.3]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-6.0.11.jar:6.0.11]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:244) ~[spring-aop-6.0.11.jar:6.0.11]
at jdk.proxy2/jdk.proxy2.$Proxy166.existsById(Unknown Source) ~[na:na]
at project.labelingtool.backend.app.member.adapter.out.persist.MemberRepository.existById(MemberRepository.java:45) ~[main/:na]
at project.labelingtool.backend.app.member.application.service.MemberService.checkExistMember(MemberService.java:55) ~[main/:na]
at project.labelingtool.backend.app.admin.adapter.in.rest.AdminFacade.getAllBills(AdminFacade.java:30) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-6.0.11.jar:6.0.11]
- 대충 파라미터가 null로 들어오고 있는 상황으로보입니다. 디버깅 모드로 찍어봤을 때 filter까지는 잘 거치나 controller에서는 파라미터가 못들어오고 있었습니다.
- 아래는 구현한 코드입니다.
// controller
@GetMapping("/bills")
public BaseResponse<AdminBills.Response> getbills(AdminBills.Request request, @Admin MemberSummary memberSummary) throws MemberException {
AdminBills.Response allBills = adminFacade.getAllBills(request, memberSummary);
return BaseResponse.send("조회가 완료되었습니다.", allBills);
}
// annotation
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Admin {
String value() default "";
}
// argument resolver
@Slf4j
@Component
@RequiredArgsConstructor
public class AdminArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Admin.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) webRequest.getNativeRequest();
Object logined = httpServletRequest.getAttribute("logined");
log.debug("test = {}",((MemberSummary)logined).getMemberId().toString());
if (logined==null) {
throw new AuthorizationServiceException("로그인이 필요한 기능입니다.");
}
if (((MemberSummary) logined).getRole()!= Role.ADMIN) {
throw new AccessDeniedException("관리자 권한이 필요한 기능입니다.");
}
return logined;
}
}
- 디버깅 모드에서 ArgumentResolver 브레이킹 포인트도 안먹고 로그도 안찍히는 것으로 보아 아예 ArgumentResolver를 거치지 않고 있음이 확인되었습니다.
발생 원인
- 어이없게도 원인은 WebMvcConfigurer에 ArgumentResolver로 등록하지 않았기 때문이었습니다.
- 어이없는 실수에 황당하지만 잊지 않기 위해서 이렇게 실수를 기록해둡니다. (내 2시간... 🥲)
대처
- ArgumentResolver를 등록하였습니다..
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginArgumentResolver());
resolvers.add(new AdminArgumentResolver());
}
// 기타등등
}
Refs.