๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

ํ”„๋กœ์ ํŠธ/Airbnb Clone

[๋กœ๊ทธ์ธ ๋ฐ ์ธ๊ฐ€] Interceptor์—์„œ ArgumentResolver๋กœ ๋ฆฌํŒฉํ† ๋งํ•˜๊ธฐ

[์„œ๋ก ]

์—์–ด๋น„์•ค๋น„ ํด๋ก  ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ, ๋กœ๊ทธ์ธ ํšŒ์›๋งŒ '์ˆ™์†Œ ๋“ฑ๋ก' ๊ณผ '์ˆ™์†Œ ์˜ˆ์•ฝ' ์„ ํ•  ์ˆ˜ ์žˆ๊ธฐ ์œ„ํ•ด(์ธ๊ฐ€๋ฅผ ์ฃผ๊ธฐ ์œ„ํ•ด) ๋กœ๊ทธ์ธํ•œ ํšŒ์›์„ ๋Œ€์ƒ์œผ๋กœ ์„ธ์…˜์„ ์‚ฌ์šฉํ•ด์„œ stateful ํ•˜๊ฒŒ ๊ตฌํ˜„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

@PostMapping("/login")
    public ApiResponse<Void> login(@RequestBody @Valid MemberLoginRequest request,
                                   HttpSession httpSession) {
        memberService.login(request.toServiceRequest());   // ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ•˜๋ฉด
        httpSession.setAttribute(LOGIN_MEMBER, true);      // ์„ธ์…˜ ๋ถ€์—ฌ
        return ApiResponse.ok();
    }

 

๊ทธ๋ฆฌ๊ณ , DispatcherServlet ์ด HandlerAdapter(ํ•ธ๋“ค๋Ÿฌ ์–ด๋Œ‘ํ„ฐ) ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— Interceptor ์˜ preHandle ์„ ๋จผ์ € ํ˜ธ์ถœํ•˜๋„๋ก ํ•ด์„œ, preHandle ๋‚ด๋ถ€์—์„œ ๋กœ๊ทธ์ธ์šฉ ์„ธ์…˜์ด ์žˆ๋Š”์ง€ ์ฒดํฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.

public class LoginCheckInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            response.setStatus(400);
            return false;
        }

        Object loginMember = session.getAttribute(LOGIN_MEMBER);
        return loginMember != null;
    }
}

 

[ArgumentResolver ๋กœ ๋ฆฌํŒฉํ† ๋ง ํ•œ ์ด์œ ]

1. Interceptor ๋กœ๋Š” Restful API ๋ฅผ ์‹๋ณ„ํ•˜์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ํŠน์ • ์ˆ™์†Œ์˜ ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๋Š” GET /stay/{stayId} ๋Š” ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ, ํŠน์ • ์ˆ™์†Œ๋ฅผ ์‚ญ์ œํ•˜๋Š” DELETE /stay/{stayId} ๋Š” ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์ด์— ๋Œ€ํ•ด Interceptor ๋ฅผ ๋“ฑ๋กํ•  ๋•Œ, addPathPatterns() ๋กœ๋Š” ํ‘œํ˜„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

 

๋ฌผ๋ก , preHandle ์•ˆ์—์„œ request method ๋ฅผ ํ™•์ธํ•ด์„œ ํ•  ์ˆ˜๋Š” ์žˆ๊ฒ ์ง€๋งŒ, ๋” ๋ณต์žกํ•ด์งˆ๊ฒƒ์ด๋ผ๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

 

2. Controller ์˜ ์ •๋ณด๋งŒ์œผ๋กœ ํ•ด๋‹น API ๊ฐ€ ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ์š”์ฒญ์ธ์ง€ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

ํ•ด๋‹น API ๊ฐ€ ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ API ์ธ์ง€ ์ˆจ๊ฒจ์ ธ ์žˆ๋‹ค๊ณ  ํŒ๋‹จํ•ด์„œ, ์ด๋ฅผ ์ฝ”๋“œ์ƒ์œผ๋กœ ๋ช…ํ™•ํžˆ ๋“œ๋Ÿฌ๋‚ด๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค.

 

[๊ฒฐ๊ณผ]

HandlerAdapter ๊ฐ€ ArgumentResolver ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๊ฐ’์ด ๋งŒ๋“ค์–ด ์ค„ ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • custom ์–ด๋…ธํ…Œ์ด์…˜ → (1) @Login
  • HandlerMethodArgumentResolver ๋ฅผ Override ํ•˜์—ฌ ArgumentResolver ๊ฐ€ ์ž‘๋™ํ•˜๋Š” ์กฐ๊ฑด ์„ค์ • → (2) LoginArgumentResolver
  • ์กฐ๊ฑด ์„ค์ •ํ•œ ArgumentResolver ๋ฅผ ๋“ฑ๋ก → (3) addArgumentResolvers(...)
(1) @Login
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}

 

(2) LoginArgumentResolver

public class LoginArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
        boolean hasMemberId = Long.class.isAssignableFrom(parameter.getParameterType());

        return hasLoginAnnotation && hasMemberId;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        HttpSession session = request.getSession(false);
        if (session == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN);
        }

        return session.getAttribute(SessionConst.LOGIN_MEMBER);
    }
}

 

 (3) addArgumentResolvers(...)

@Configuration
public class WebConfig implements WebMvcConfigurer {

    (...์ค‘๋žต)
    
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new LoginArgumentResolver());
    }
}

 

 

๋กœ๊ทธ์ธ ์—ฌ๋ถ€๊ฐ€ ํ•„์š”ํ•œ API ์—์„œ Session ์„ ํ™•์ธํ•˜๋Š” ์ค‘๋ณต ๋กœ์ง์„ ์ œ๊ฑฐํ•˜๊ณ , ์ฝ”๋“œ์˜ ๋ช…ํ™•์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. 

@PostMapping("/reservation")
public ApiResponse<ReservationResponse> reserve(
        @Login Long userId,
        @RequestBody @Valid ReservationAddRequest request) {
    ReservationResponse response = reservationService.reserve(request.toServiceRequest(userId));
    return ApiResponse.ok(response);
}