코딩/sparta TIL

TIL 58 : Resolver(리졸버)란? - 다시 공부

americanoallday 2025. 5. 15. 12:02

"리졸버(Resolver)"는 메서드의 파라미터 값을 어떻게 채울지를 결정하는 객체.


🎯 리졸버(Resolver)란?

컨트롤러에서 이런 코드 

@GetMapping("/todos")
public ResponseEntity<?> getTodos(@AuthenticationPrincipal AuthUser user) {
    ...
}

여기서 AuthUser user 값은 Spring이 자동으로 채워줌.
그 역할을 하는 게 HandlerMethodArgumentResolver라는 인터페이스.

즉, "컨트롤러 메서드 파라미터 값을 어떻게 구할까?"를 해결(Resolve)하는 객체라서 리졸버(Resolver) 라고 부름.


✅ Spring Security 기본 Resolver 예시

@AuthenticationPrincipal은 Spring Security에서 제공하는 기본 리졸버가 있어서,
SecurityContextHolder.getContext().getAuthentication().getPrincipal()에서 값을 꺼내서 자동으로 넣어줌.

그래서 Security 기반에서는 무조건 @AuthenticationPrincipal이 가장 안정적으로 작동함.


✅ 내가 만들었던 커스텀 Resolver

@Auth + AuthUserArgumentResolver도 사실 커스텀 리졸버.

public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver

직접 만든 리졸버는

  • request.getAttribute("userId") 같은 값을 HttpServletRequest에서 가져왔고
  • @Auth가 붙어 있는 파라미터를 보고 값을 만들어 넣어줬음.

✅ 결론 정리

리졸버 종류  어디서 값 가져옴  예시
Spring Security 기본 리졸버 SecurityContextHolder → Authentication → Principal @AuthenticationPrincipal
커스텀 리졸버 request attribute (request.setAttribute()) @Auth + AuthUserArgumentResolver

 


Spring MVC의 HandlerMethodArgumentResolver 전체 동작 흐름

🛤️ Spring MVC Argument Resolver 전체 흐름

1. 클라이언트가 요청을 보낸다.

  • 예를 들면 /todos에 POST 요청 보냄.

2. DispatcherServlet이 요청을 받는다.

  • Spring MVC의 모든 요청은 DispatcherServlet이 제일 먼저 받는다.

3. HandlerMapping을 통해 어떤 Controller, 어떤 메서드로 요청을 보낼지 찾는다.

  • 예를 들어 TodoController#saveTodo() 같은 메서드를 찾아냄.

4. HandlerMethodArgumentResolver가 작동하기 시작한다.

"컨트롤러 메서드 파라미터에 어떤 값을 넣어줄까?" 를 결정하는 단계야.

Spring은 등록된 여러 ArgumentResolver 목록을 가지고 있는데,
요청에 필요한 파라미터마다 순서대로 supportsParameter()를 호출해서

  • 이 파라미터를 내가 처리할 수 있나?
  • 못하면 다음 Resolver에게 넘긴다.

✅ supportsParameter()가 true를 리턴하면 → resolveArgument()를 호출해서 실제 값을 생성해낸다.


5. Spring Security가 개입하는 경우

  • @AuthenticationPrincipal을 붙이면
  • Spring Security의 기본 AuthenticationPrincipalArgumentResolver가 작동함.

그 리졸버가

SecurityContextHolder.getContext().getAuthentication().getPrincipal()

여기서 Principal 객체(= 로그인한 사용자 정보)를 꺼내서 메서드 파라미터로 넘겨줌.

→ 그래서 Spring Security가 알아서 값을 넣어줄 수 있었던 것.


6.  커스텀 Resolver가 개입하는 경우

만약 커스텀 어노테이션 @Auth를 썼으면

  • 만든 AuthUserArgumentResolver가 supportsParameter를 통해 잡아채고
  • resolveArgument에서 직접 request.getAttribute() 같은 걸로 값을 채워줬어야 함.

🎯 요약

요청 → DispatcherServlet →
  HandlerMapping → Controller 메서드 찾음 →
    각각의 파라미터마다
      1. 여러 ArgumentResolver 순회
      2. supportsParameter()로 담당자 찾기
      3. resolveArgument()로 실제 값 생성
  → Controller 메서드 실행

🔥 요약

  • Resolver = Controller 파라미터를 어떻게 채울지 결정하는 객체
  • Spring Security 기본 Resolver = Principal을 SecurityContextHolder에서 꺼내줌
  • 커스텀 Resolver = request.getAttribute에서 수동으로 꺼냈음

🌟 1️⃣ Spring MVC + Spring Security 요청 처리 흐름

클라이언트 요청 → DispatcherServlet → FilterChain → Controller

✔️ 1. 클라이언트 요청

POST /todos
Authorization: Bearer eyJhbGci...

✔️ 2. DispatcherServlet이 FilterChain 호출

DispatcherServlet
   ↓
FilterChainProxy (Spring Security FilterChain)

✔️ 3. Spring Security FilterChain 동작

순서 예시

[일반 Filter]
 → JwtFilter (직접 만든 Filter → SecurityContextHolder.setAuthentication())
 → Security Filters
      - SecurityContextPersistenceFilter
      - UsernamePasswordAuthenticationFilter
      - AuthorizationFilter
      ...

직접 만든 JwtFilter에서 이 코드가 실행됨:

SecurityContextHolder.getContext().setAuthentication(auth);

즉, SecurityContextHolder에 UsernamePasswordAuthenticationToken(authUser, null, authorities)가 들어감.
이게 이후 인증 정보로 계속 유지됨.


✔️ 4. DispatcherServlet → HandlerMapping → Controller 찾음

DispatcherServlet
   ↓
HandlerMapping
   ↓
@Controller 클래스, @RequestMapping 메서드 찾기

ex)

@PostMapping("/todos")
public ResponseEntity<?> saveTodo(@AuthenticationPrincipal AuthUser authUser, ...) 

✔️ 5. HandlerMethodArgumentResolver 작동

메서드 파라미터마다
→ ArgumentResolver 리스트 순서대로 supportsParameter() 체크

기본 리스트 예시

  1. RequestParamMethodArgumentResolver (@RequestParam)
  2. PathVariableMethodArgumentResolver (@PathVariable)
  3. AuthenticationPrincipalArgumentResolver (@AuthenticationPrincipal ⭐)
  4. 커스텀 AuthUserArgumentResolver (❌ 지금은 등록 안 됨 → 패스)
  5. 기타...

✅ @AuthenticationPrincipal 붙은 파라미터 → AuthenticationPrincipalArgumentResolver에서 처리

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();

principal (= JwtFilter에서 set했던 AuthUser) 가 자동으로 파라미터에 주입됨. 👉 그래서 @AuthenticationPrincipal은 정상 작동


✔️ 6. 컨트롤러 호출

모든 파라미터가 주입되고 Controller#saveTodo() 호출


🌟 3️⃣ 정리

@Auth + AuthUser @AuthenticationPrincipal

동작 방식 request attribute 직접 꺼냄 SecurityContextHolder에서 Principal 꺼냄
Resolver 직접 만든 AuthUserArgumentResolver Spring Security 기본 AuthenticationPrincipalArgumentResolver
Security 연동 ✅ 완벽 연동

🎯 요약

클라이언트
   ↓
DispatcherServlet
   ↓
FilterChainProxy (Security Filter)
   ↓
JwtFilter → SecurityContextHolder에 Authentication 세팅
   ↓
HandlerMapping → Controller 찾기
   ↓
HandlerMethodArgumentResolver → @AuthenticationPrincipal → SecurityContextHolder에서 principal 꺼내서 파라미터 주입
   ↓
Controller 실행