요청로그 및 로그인 인터셉터 구현하기
들어가며
이전 포스팅까지는 애플리케이션을 실행하고 게시판을 사용할 때 아무런 로그가 나오지 않았습니다. 이제 각 기능을 실행할 때마다 요청과 응답 로그를 출력하는 요청 로그를 구현할 것입니다. 또한 현재 "localhost:8080/boardWrit" URL로 직접 접속하면 로그인하지 않은 사용자도 해당 페이지로 이동할 수 있어 보안 문제가 발생합니다. 이제 인터셉터 기능을 활용하여 공통적으로 로그인이 필요한 페이지를 설정하여, 로그인하지 않은 사용자가 해당 페이지에 접속하지 못하고 로그인 폼으로 이동하는 로그인 인터셉터를 구현할 것입니다.
사전 준비
만약 게시판 프로젝트를 하시려는 분은
게시글 페이징처리하기 까지 따라해 만들어주시기 바랍니다!
[SpringBoot] 무작정 (REST API)CRUD 게시판을 만들어 보자 게시글 페이징 처리하기 [8]
[8] 스프링 데이터 JPA를 사용해 게시글 페이징 처리 들어가며 이전 포스팅까지 삭제하기 기능을 구현해 봤습니다. 이번 포스팅에서는 스피링 데이터 JPA를 사용해 페이징 처리를 해보겠습니다.(JP
back-stead.tistory.com
전체 코드는 깃에 올려두었습니다.
GitHub - CHISANW/message-board
Contribute to CHISANW/message-board development by creating an account on GitHub.
github.com
구현할 기능
모든 기능에 요청과 응답 로그를 남긴다.
로그인 인터셉터를 통해 로그인을 한 후에 기능을 이용하게 한다.
[Spring] 스프링에서의 필터(filter)와 인터셉터(Interceptor)
스프링에서 사용하는 filter와 Interceptor에 대해서 알아보자 들어가며 필터(Filter)와 인터셉터(Interceptor)는 모두 웹 애플리케이션에서 클라이언트의 요청과 응답을 가로채어 특정 작업을 수행하는
back-stead.tistory.com
로그 출력 구현
요청과 응답 로그를 출력하는것은 많은 도움이 된다.
1. 디버깅 및 오류 해결
2. 성능 모니터링
3. 보안감시
4. 사용자 활동 추적
이러한 이점을 얻기 위해서 요청과 응답이 로그가 모든 메서드에 공통적으로 출력되게 해 보자
이번에는 공통된 로그를 출력하기 위해 두 가지 방법이 있습니다. 하나는 서블릿이 제공하는 필터(Filter)를 사용하는 방법이고, 다른 하나는 스프링이 제공하는 인터셉터(Interceptor)를 사용하는 방법입니다. 이번에는 인터셉터를 통해 구현할 예정입니다. 만약 필터와 인터셉터가 어떻게 동작하는지 잘 모르신다면, 위의 "필터와 인터셉터란?"을 참고해주세요
LogInterceptor
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
private static final String LOG_ID = "logId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString().substring(0,5);
request.setAttribute(LOG_ID,uuid);
log.info("요청 [{}] [{}] [{}] [{}]",uuid,requestURI, request.getDispatcherType(),handler);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String logId = (String) request.getAttribute(LOG_ID);
log.info("응답 [{}] [{}] [{}] [{}]",logId,requestURI,request.getDispatcherType(),handler);
if (ex!=null){
log.error("에러발생 error!!",ex);
}
}
}
HandlerInterceptor을 구현한 LogInterceptor입니다.
요청이 들어오면 preHandle이 실행되어 랜덤으로 UUID를 생성해 사용자를 부여하고 요청 URL와 요청타입과 핸들러 정보를 로그로 남기게 됩니다.
응답이 들어오면 상수로 정의해돈 "logId"와 요청 URI, 요청 타입, 핸들러정보를 응답로그에 반환하며, 만약 Exception이 발생하면 에러발생이라는 로그를 남기게 해 줍니다.
이 로그 기능을 통해서 오류가 발생한다면 요청과 응답로그를 보고 어디서 오류가 발생했는지 빠르게 찾을 수 있는 장점이 생깁니다.
이제 만든 LogInterceptor을 사용하기 위해서는 스프링 컨테이너에 직접 등록해줘야 합니다.
WebConfig
@Configuration
public class webConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**","/*.ico","/error","error-page/**");
}
}
설정 파일을 만들고 addInterceptros를 오버라이딩해서 커스텀해줍니다.
registry를 통해서 만든 로그인터셉터를 추가해 주고 순서와 적용할 패턴 제외할 패턴을 작성해 줍니다.
이제 애플리케이션을 실행하고 로그인 폼으로 이동하게 되면 로그가 출력되는 것을 볼 수 있습니다.
만약 오류가 있었을 때는 다음과 같이 [exception] ex 로그를 남기게 됩니다. 아직 @HandlerInception을 구현하지 않았기 때문에 오류내용은 설명하지 않겠습니다.
이렇게 간단하게 로그를 출력하는 인터셉터를 구현해 봤습니다.
로그인 인터셉터
지금까지는 만약 글쓰기 버튼을 클릭했을 때 "로그인 후 사용하수있습니다"라고 메시지가 나오게했지만 이것은 클라리언트에서만 적용한 검증이였다. 만약 직접 URL로 접속을 하게되면 접속하는것을 볼수있다. 이러한 것을 막기 위해서 로그인 인터셉터를 적용해 로그인하지 않은 사용자가 로그인후 사용할 수 있는 폼에 접근하려고할때 로그인 인터셉터가 로그인유뮤루를 확인하고 로그인 사용하지만 사용할수 있도록 구현해 볼 것입니다.
LoginCheckInterceptor
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
HttpSession session = request.getSession(false);
Member loginMember = (Member) session.getAttribute("loginMember");
if (session == null || loginMember == null || loginMember.getUsername() == null) {
log.info("미사용자 요청");
response.sendRedirect("/login?redirectURL=" + requestURI);
return false;
}
return true;
}
}
로그 인터셉터와 마찬가지로 HaandlerInterceptor을 구현한 LoginCheckInterceptor을 만들었습니다.
prerHandle를 통해서 기능을 시작하기 전에 호출되며 세션에서 "loginMeber"로 저장된 사용자가 있다면 true 반환해 정상적으로 호출되게 하며 만약 세션에 사용자가 없다면 "미사용자 요청"이라는 로그를 출력하고 로그인 페이지로 요청한 URL정보를 함께 로그인 페이지로 리다이렉트 합니다. (로그인 후 원래 요청 페이지로 이동할 수 있도록 requestURL을 포함)
만든 로그인인터셉터를 스프링 컨테이너에 직접 등록해 줍니다.
WebConfig
@Configuration
public class webConfig implements WebMvcConfigurer {
...로그 인터셉터
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns("제외할 URI");
}
}
설정파일에 만든 로그인터셉터를 등록해 주며 로그 인터셉터가 실행된 후에 로그인 인터셉터가 실행하도록 order(2)로 설정해 주고 적용할 패턴가 제외할 패턴 URI를 추가해 줍니다.
로그인을 성공하면 원래 있던 페이지로 넘어가기 위해서 로그인 컨트롤러를 수정해 줍니다.
MemberController
@GetMapping("/login")
public String getLogin(@RequestParam(defaultValue = "/") String redirectURL,HttpServletRequest request, Model model){
...
//추가
String referer = request.getHeader("Referer");
request.getSession().setAttribute("referer", referer);
// 이전 페이지의 URL을 쿼리 매개변수로 전달
request.getSession().setAttribute("prevPage",redirectURL);
return "member/login";
}
@PostMapping("/login")
public String postLogin(@ModelAttribute("login") LoginDto loginDto,
HttpSession session,HttpServletRequest request, Model model){
...
if (login){
...
//추가
String prevPage = (String) request.getSession().getAttribute("prevPage");
String rePrevPage = (String) request.getSession().getAttribute("referer");
// 로그인 후에는 전달된 redirectURL로 리다이렉트
return "redirect:" + (prevPage.equals("/") ? rePrevPage : prevPage);
}
...
return "member/login";
}
해당 코드를 추가합니다.
그러면 로그인페이지로 이동하고 로그인을 성공하면 원래 있던 페이지로 갈 수 있습니다.
동작화면
다음으로
이렇게 인터셉터를 통해 로그기능과 로그인 체크 기능을 구현해 봤습니다.
부족한 점이 있다면 댓글로 알려주시면 한번 고쳐보도록 해보겠습니다! 감사힙니다!