μ•ˆλ…•ν•˜μ„Έμš” Mamboμž…λ‹ˆλ‹€. μ˜€λŠ˜μ€ μŠ€ν”„λ§ MVC λͺ¨λ“ˆμ— ν¬ν•¨λ˜μ–΄μžˆλŠ” HandlerInterceptor μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ–Έμ œ μ‚¬μš©ν•˜λŠ”κ°€μ— λŒ€ν•΄μ„œ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.

HandlerInterceptor

A HandlerInterceptor gets called before the appropriate HandlerAdapter triggers the execution of the handler itself. This mechanism can be used for a large field of preprocessing aspects, e.g. for authorization checks, or common handler behavior like locale or theme changes. Its main purpose is to allow for factoring out repetitive handler code.

HandlerInterceptorλŠ” 컨트둀러 ν•Έλ“€λŸ¬ ν•¨μˆ˜μ— λŒ€ν•œ μ „μ²˜λ¦¬ λ™μž‘μ„ μˆ˜ν–‰ν•  수 μžˆλŠ” 방법을 μ œκ³΅ν•©λ‹ˆλ‹€. 필터도 μ „μ²˜λ¦¬ λ™μž‘μ„ μˆ˜ν–‰ν•  수 μžˆμ§€λ§Œ web.xml에 μ •μ˜λ˜λŠ” 필터와 λ‹€λ₯΄κ²Œ HandlerInterceptorλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ»¨ν…μŠ€νŠΈμ—μ„œ κ΄€λ¦¬ν•˜λ―€λ‘œ μš”μ²­ 정보λ₯Ό λΆ„μ„ν•˜μ—¬ μ‚¬μš©μžλ₯Ό μΈμ¦ν•˜κ±°λ‚˜ 응닡 λ·°λ₯Ό λ Œλ”λ§ν•˜κΈ° 전에 λΆ€κ°€ 데이터λ₯Ό μ£Όμž…ν•˜λŠ” λ™μž‘μ„ μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄, 기본으둜 μ œκ³΅λ˜λŠ” HandlerInterceptor κ΅¬ν˜„μ²΄μΈ LocaleChangeInterceptorλŠ” λ‘œμΌ€μΌ νŒŒλΌλ―Έν„°μ— 따라 ν˜„μž¬ λ‘œμΌ€μΌμ„ λ³€κ²½ν•˜λŠ” λ™μž‘μ„ μˆ˜ν–‰ν•˜μ£ .

정적 λ¦¬μ†ŒμŠ€ νŒ¨ν„΄ μ œμ™Έ

ResourceHandlerRegistryλŠ” 정적 λ¦¬μ†ŒμŠ€λ₯Ό λ°°ν¬ν•˜κΈ° μœ„ν•œ ν•Έλ“€λŸ¬λ₯Ό λ“±λ‘ν•˜λŠ” 것을 μ§€μ›ν•˜λŠ” ν΄λž˜μŠ€μž…λ‹ˆλ‹€. μ΄λ•Œ, λ¦¬μ†ŒμŠ€ ν•Έλ“€λŸ¬κ°€ μ²˜λ¦¬ν•  경둜λ₯Ό λ§€μΉ­ν•˜κΈ° μœ„ν•˜μ—¬ AntPathMatcher λ˜λŠ” PathPattern을 μ‚¬μš©ν•˜κ²Œ λ˜λŠ”λ° Ant μŠ€νƒ€μΌμ˜ νŒ¨ν„΄ 맀칭을 μ‚¬μš©ν•˜λ―€λ‘œ νŠΉμ • νŒ¨ν„΄μ„ μ œμ™Έν•˜κΈ° μœ„ν•œ Regex 같은 방식을 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

λ¦¬μ†ŒμŠ€ ν•Έλ“€λŸ¬κ°€ μ²˜λ¦¬ν•˜λŠ” κ²½λ‘œμ— λŒ€ν•΄μ„œ νŠΉμ • νŒ¨ν„΄μ„ μ œμ™Έν•˜κΈ° μœ„ν•΄μ„œλŠ” ν•΄λ‹Ή νŒ¨ν„΄μ„ μ²˜λ¦¬ν•˜μ§€ μ•Šλ„λ‘ νŒ¨ν„΄λ³„λ‘œ λ“±λ‘ν•˜μ—¬μ•Όν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ, μ΄λ ‡κ²Œ μ˜¬λ°”λ₯Έ νŒ¨ν„΄λ§ˆλ‹€ λ¦¬μ†ŒμŠ€ ν•Έλ“€λŸ¬λ₯Ό λ“±λ‘ν•˜λŠ” 것은 λΆˆνŽΈν•©λ‹ˆλ‹€.

ResourceHandlerRegistry
@Configuration(proxyBeanMethods = false) public class MvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**").addResourceLocations("classpath:/static/images/"); } }

μœ„ μ˜ˆμ‹œλŠ” /images/** νŒ¨ν„΄μ˜ κ²½λ‘œμ— λŒ€ν•΄μ„œ 클래슀패슀의 static/images 폴더에 μžˆλŠ” 정적 λ¦¬μ†ŒμŠ€λ‘œ μ²˜λ¦¬ν•˜κΈ° μœ„ν•œ ν•Έλ“€λŸ¬λ₯Ό λ“±λ‘ν•©λ‹ˆλ‹€. images ν΄λ”μ—λŠ” 이미지 파일만 μžˆλ‹€κ³  κ°€μ •ν–ˆμœΌλ‚˜ 개발자의 μ‹€μˆ˜λ‘œ images 폴더에 이미지가 μ•„λ‹Œ λ™μ˜μƒ νŒŒμΌμ΄λ‚˜ μ˜€λ””μ˜€ 파일이 λ“€μ–΄μžˆλ‹€λ©΄ ν•΄λ‹Ή λ¦¬μ†ŒμŠ€λ„ μ²˜λ¦¬ν•˜κ²Œ λ©λ‹ˆλ‹€.

μ΄λ ‡κ²Œ νŠΉμ • νŒ¨ν„΄μ„ λ°©μ§€ν•΄μ•Όν•˜λŠ” κ²½μš°μ— HandlerInterceptorλ₯Ό λ“±λ‘ν•˜μ—¬ μš”μ²­μ„ λΆ„μ„ν•˜μ—¬ λ¦¬μ†ŒμŠ€λ₯Ό μ‘λ‹΅ν•˜μ§€ μ•Šλ„λ‘ μ „μ²˜λ¦¬ λ™μž‘μ„ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 이미지 파일 νŒ¨ν„΄μ΄ μ•„λ‹ˆλΌλ©΄ μš”μ²­μ„ 404 Not Found둜 μ²˜λ¦¬ν•˜λŠ” λ™μž‘μ„ μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(onlyServeImagesHandlerInterceptor()).addPathPatterns("/images/**");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/images/**").addResourceLocations("classpath:/static/images/");
    }

    @Bean
    OnlyServeImagesHandlerInterceptor onlyServeImagesHandlerInterceptor() {
        return new OnlyServeImagesHandlerInterceptor();
    }

    public static class OnlyServeImagesHandlerInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String requestURI = request.getRequestURI();
            if(!requestURI.matches(".*\\/.*\\.(jpg|jpeg|png|gif)")) {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                return false;
            }
            return true;
        }
    }
}

μœ„ 인터셉터가 μ •λ§λ‘œ 이미지 νŒŒμΌμ— λŒ€ν•΄μ„œλ§Œ μ²˜λ¦¬ν•  수 있게 사전 처리λ₯Ό μˆ˜ν–‰ν•˜λŠ”μ§€ ν™•μΈν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

μœ„ κ²°κ³Όλ₯Ό 보면 이미지 νŒŒμΌμ€ 정적 λ¦¬μ†ŒμŠ€λ‘œ λ°°ν¬λ˜λ„λ‘ μ²˜λ¦¬λ˜μ—ˆμ§€λ§Œ λ™μ˜μƒ νŒŒμΌμ€ μ²˜λ¦¬λ˜μ§€ μ•Šκ²Œ 된걸 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

λ·° 응닡 μ‹œ λΆ€κ°€ 정보 μ£Όμž…

λ‘λ²ˆμ§Έλ‘œλŠ” λ·°λ₯Ό μ‘λ‹΅ν•˜λŠ” ν•Έλ“€λŸ¬ ν•¨μˆ˜μ— λŒ€ν•˜μ—¬ λΆ€κ°€ 정보λ₯Ό μ£Όμž…ν•˜λŠ”λ°μ— μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒμ˜ μ½”λ“œλŠ” λ·°λ₯Ό μ‘λ‹΅ν•˜κ²Œλ˜λŠ” ν•Έλ“€λŸ¬ ν•¨μˆ˜μ— μ‚¬μš© κ°€λŠ₯ν•œ 언어와 ν•¨κ»˜ ν˜„μž¬ λ‘œμΌ€μΌμ— λŒ€ν•œ λ©”μ‹œμ§€ μ½”λ“œ λͺ©λ‘μ„ μ£Όμž…ν•˜λŠ” 인터셉터 μ˜ˆμ‹œμž…λ‹ˆλ‹€.

InjectionLocalesInterceptor
public static class InjectionLocalesInterceptor extends HandlerInterceptorAdapter { private MessagePool messagePool = null; @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { if(modelAndView != null && messagePool != null) { Locale locale = messagePool.getLocale(); if(locale == null) { locale = Locale.getDefault(); } modelAndView.addObject("lang", locale.getLanguage()); modelAndView.addObject("locale", locale.toString()); if(handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Class<?> beanType = handlerMethod.getBeanType(); Controller controller = beanType.getAnnotation(Controller.class); if(controller != null) { modelAndView.addObject("locales", messagePool.getLocales()); modelAndView.addObject("messages", messagePool.getMessagesInJson().toString()); } } } super.postHandle(request, response, handler, modelAndView); } }

이제 ν…œν”Œλ¦Ώ μ—”μ§„μ—μ„œ μ£Όμž…λœ λΆ€κ°€ 정보λ₯Ό μ‚¬μš©ν•˜μ—¬ λ Œλ”λ§μ„ ν•  수 있게 λ©λ‹ˆλ‹€.

인증을 μœ„ν•œ 인터셉터

또 λ‹€λ₯Έ μΈν„°μ…‰ν„°μ˜ ν™œμš© λ°©μ•ˆμ€ 인증 μ²˜λ¦¬μž…λ‹ˆλ‹€. μ‚¬μš©μžμ—κ²Œ 이메일 λ˜λŠ” μΉ΄μΉ΄μ˜€ν†‘ λ©”μ‹œμ§€λ“±μ„ λ°œμ†‘ν•˜μ—¬ μ–΄λ–€ 행동을 ν•  수 μžˆλŠ” 링크λ₯Ό 주게 λœλ‹€κ³  가정해보면 ν•΄λ‹Ή 링크λ₯Ό 톡해 λ“€μ–΄μ˜€λŠ” μ‚¬μš©μžμ€ 인증을 μˆ˜ν–‰ν•˜κΈ° 전일 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λ•Œ μ£Όμ–΄μ§€λŠ” 링크에 μž„μ‹œμ μœΌλ‘œ 인증에 μ‚¬μš©ν•  수 μžˆλŠ” λ§Œλ£Œμ„± 토큰 νŒŒλΌλ―Έν„°λ₯Ό ν¬ν•¨μ‹œν‚΄μœΌλ‘œμ¨ μ‚¬μš©μžλ₯Ό μΈμ¦ν•˜λŠ” λ‘œμ§μ„ μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ½”μΈμ›μ˜ 이메일 μΈμ¦ν•˜κΈ° λ²„νŠΌ

μœ„ μ˜ˆμ‹œ μ΄λ―Έμ§€μ˜ 이메일 μΈμ¦ν•˜κΈ° λ²„νŠΌμ— λŒ€ν•œ λ§ν¬μ—λŠ” μ½”μΈμ›μ—μ„œ 뢄석할 수 μžˆλŠ” μ–΄λ– ν•œ νŒŒλΌλ―Έν„°μ— 인증을 μœ„ν•œ 식별 정보가 μžˆμ„κ±°λΌ μΆ”μΈ‘ν•©λ‹ˆλ‹€.

λ‹€μŒμ€ 토큰 νŒŒλΌλ―Έν„° 유무λ₯Ό ν™•μΈν•˜κ³  토큰 νŒŒλΌλ―Έν„°λ₯Ό λΆ„μ„ν•˜μ—¬ 인증을 μˆ˜ν–‰ν•˜λŠ” 인터셉터 κ΅¬ν˜„μ˜ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

TokenBasedAuthenticationInterceptor
public static class TokenBasedAuthenticationInterceptor implements HandlerInterceptor { private static final String TOKEN_PARAMETER_NAME = "token"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String parameter = request.getParameter(TOKEN_PARAMETER_NAME); if(parameter != null && !parameter.equals("")) { // NOTE: 토큰 νŒŒλΌλ―Έν„° 뢄석 및 검증 String token = analyticsToken(parameter); boolean valid = validateToken(token); if(valid) { // NOTE: 토큰 기반 인증 처리 authenticationByToken(token); } return valid; } return false; } }

일정 μ‹œκ°„μ΄ 만료되면 ν•΄λ‹Ή ν† ν°μœΌλ‘œλŠ” 인증할 수 μ—†κ³  이미 인증을 μˆ˜ν–‰ν•˜μ—¬ μ‚¬μš©μ΄ μ™„λ£Œλœ ν† ν°μœΌλ‘œ ν™•μΈν•˜λŠ” 것은 토큰 νŒŒλΌλ―Έν„° 뢄석 및 검증 λ‘œμ§μ— ν¬ν•¨λ κ²λ‹ˆλ‹€.

κ°„λ‹¨ν•˜κ²Œ HandlerInterceptor의 ν™œμš© λ°©μ•ˆ 3가지λ₯Ό ν™•μΈν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ§ˆλ‹€ μš”κ΅¬μ‚¬ν•­μ΄ λ‹€λ₯΄κ³  무ꢁ무진 ν•˜κΈ° λ•Œλ¬Έμ— HandlerInterceptorλŠ” 더 λ‹€μ–‘ν•œ λ°©μ‹μœΌλ‘œ ν™œμš©ν•  수 μžˆμ„ κ²λ‹ˆλ‹€. μ΄μƒμœΌλ‘œ HandlerInterceptor은 μ–Έμ œ μ‚¬μš©ν•˜λ‚˜μš”?λ₯Ό λ§ˆμΉ˜κ² μŠ΅λ‹ˆλ‹€. κ°μ‚¬ν•©λ‹ˆλ‹€.