개발 일기

인터셉터 / 커스텀 애노테이션 활용하여 로그인 강제 본문

Tech/배워서 남주기

인터셉터 / 커스텀 애노테이션 활용하여 로그인 강제

flow123 2022. 9. 9. 09:50

상황

인증 기능을 구현할 때, 로그인이 필요한 API에 인증 처리를 강제해야 했습니다.

기존 코드는 인터셉터의 addPathPatterns로 모든 경로를 더하고, excludePathPatterns로 일부 경로만 제외하여 구현했습니다.

문제

위의 방식은 로그인이 필요 없는 경로를 추가할 때마다 직접 입력해줘서, 오타가 발생할 위험이 있었습니다.

이러한 위험을 줄이면서, 컨트롤러에서 로그인 필요 여부를 명시적으로 표현하는 방법을 고민하였습니다.

해결 /결과

@LoginCheck 라는 커스텀 애노테이션을 만들었습니다.

Retention Policy를 설정하여, 런타임까지 객체를 유지 했습니다.

메서드 단위로 적용함으로써, 인터셉터를 거쳐갈 때 @LoginCheck가 붙은 메서드는 로그인 여부를 확인하도록 강제했습니다.

 

ProductController.class 

제품을 등록하는 클래스. 

@RestController
@RequestMapping("/products")
public class ProductController {

  private ProductService productService;

  public ProductController(ProductService productService) {
    this.productService = productService;
  }

  @Authentication(authority = Role.SELLER)
  @LoginCheck
  @PostMapping("/register")
  public void register(@AuthenticatedUser LoginUser user,
      @RequestBody @Valid ProductRegisterRequest request) {
    productService.register(user, request);
log.info(user.getEmail() + "로그인된 유저임을 확인");
  }
}

LoginCheckInterceptor.java

 

HandlerMethod는 @RequestMapping과 그 하위 어노테이션(@GetMapping, @PostMapping 등)이 붙은 메소드의 정보를 추상화한 객체입니다. 컨트롤러에 @LoginCheck 라는 어노테이션이 붙어있는지 확인하고,

있다면 세션매니저를 통해 로그인을 하는 authenticateLogin을 강제합니다. 

 

preHandler의 응답이 true 라면 다음으로 진행하지만, 

false 일 경우에는 preHandler만 호출되고 끝납니다. 

이후 인터셉터, 핸들러 어댑터 등이 호출되지 않습니다. 

package com.example.freshcart.authentication.infrastructure.web.interceptor;

import com.example.freshcart.authentication.application.LoginUser;
import com.example.freshcart.global.exception.UnauthorizedRequestException;
import com.example.freshcart.authentication.annotation.LoginCheck;
import com.example.freshcart.authentication.application.SessionManager;
import com.fasterxml.jackson.core.JsonProcessingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

/**
 * 로그인 인터셉터 - @LoginCheck 가 붙은(인증을 거쳐야 하는 요청이) 인증을 거치도록 강제
 */

@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {

  private SessionManager sessionManager;

  public LoginCheckInterceptor(SessionManager sessionManager) {
    this.sessionManager = sessionManager;
  }

  /**
   * preHandler의 응답값이 true 이면, 다음으로 진행한다. false 이면, 다른 인터셉터, 핸들러 어댑터 등이 호출되지 않는다. preHandler만 호출되고
   * 끝남. Product 등록은 Seller만 가능하기 때문에 (1) 로그인인지 먼저 체크하고, (2) user 객체의 role 이 seller인지 확인.
   */
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    HandlerMethod method = (HandlerMethod) handler;

    if (method.getMethodAnnotation(LoginCheck.class) != null) {
      authenticateLogin(request);
    }
    return true;
  }


  public LoginUser authenticateLogin(HttpServletRequest request) throws JsonProcessingException {
    String requestURI = request.getRequestURI();
    log.info(requestURI + "인터셉터 - 인증 체크 적용");
    LoginUser user = sessionManager.getSession(request);
    if (user == null) {
      log.info("인터셉터 - 유저 정보가 없습니다" + requestURI);
      throw new UnauthorizedRequestException();
    }
    return user;
  }
}

참고: 스프링 MVC 1편 김영한님 강의 

Comments